JS-深拷贝

在JS中使用变量,使用=号拷贝,如

obj1 = obj2

是浅拷贝,即改变obj1内容的时候也会改变obj2.

有时候我们是不希望看到这种情况的,JS使用深拷贝有很多方法,介绍一个简单好用的

var obj1 = JSON.parse(JSON.stringify(obj2));

这种用法会破坏obj2的构造类型,但一般情况是足够了。

使用YUIdoc

最近遇到任务,需要对外提供接口。发现最困难的事是写文档,因为代码经常在修改,每天更新word很困难。然后就发现了文档神器YUI doc,以及神器的剑鞘smartdoc:http://www.cnblogs.com/zhh8077/p/4010991.html。

简单介绍一下使用方法和遇到的坑。

YUI doc可以看成一种标准,主要看这里http://www.cnblogs.com/zhh8077/p/4011769.html或者YUI doc的官方文档。

module写法

/**
 * XX模块,包括以下几部分:
 *
 * account-账号管理模块</br>
 * pay-支付管理模块</br>
 *
 *
 * @module XX
 */

class写法

/**
 * account-账号管理模块
 * @class account
 */

method常用写法

     /**
      * 登陆
      * @method login
      * @param param {Object}  登陆相关信息</br>
      *  @param param.accountName {string}   用户名
      *  @param param.password {string}   密码
      * @example
      *    // example
      *    account.login({
      *        accountName: userName,
      *        password:password,
      *    });
      */

其中 param.xx是二级参数的写法。

event与method一样

常量

/**
 * ACCOUNT_STATUS_LOGOUT是一个常量,账号状态为登出,value = "logout"
 * @property ACCOUNT_STATUS_LOGOUT
 * @final
 * @type string
 */

smartdoc的使用就比较简单了,参考其主页文档。

这里主要想说的是 使用过程中遇到的一些坑。

1.生成的method event等等按照字母顺序排列,而不是按照我们文档中的顺序排列。

.YUIDoc生成的API文档目录不按源文件注释顺序

YUIDoc默认将所有的class、method、properties、events等按字母进行排序。

而且这个是在生成文档时进行排序的,所以如果除去按字母进行排序这种默认行为。

就必须修改YUIDoc的工具文件。修改C:\Users\Administrator\AppData\Roaming\npm\node_modules\yuidocjs\lib 下的 builder.js 文件

第1313-1316行,就是这几句罪魁祸首,注释掉后就可以按源文件注释顺序,当然你也可以自己弄其他排序方法。

方法源自:http://www.cnblogs.com/lovesong/p/3341453.html

2.event的example不显示。使用smartdoc生成文档后,其他地方都挺好,但event的example就是显示不出来。chrome上面,直接F12,看document,看到example部分,display:none。method增加了一个active的class,display:block。很简单,看看这个css是在哪里写的。

C:\Users\用户名\AppData\Roaming\npm\node_modules\smartdoc\theme-smart\assets\css

路径下的main.css。改一下其中的example的display就可以了。

修改整个页面的布局也是修改这里。这里就是我们使用的主题,自己可以改,也可以搞一套。

 

WebStorm下配置less环境

Webstorm是一个很优秀的js IDE。今天打算学习less的时候,创建一个less文件后发现webstorm提示是否AddWatcher,点开后发现是一个自动编译less文件的工具,可惜不能直接使用,需要简单配置一下。

所有资料来自于

https://www.jetbrains.com/webstorm/help/transpiling-sass-less-and-scss-to-css.html。

洋洋洒洒一大坨,读起来有点费劲。实际操作了一下,发现简单的要命。

首先,你要装好node环境,没装的请自行百度。

1.View-ToolWindows-Terminal

2.输入 npm install -g less

搞定。

 

再次打开AddWatcher窗口,你就发现所有选项已经自动填好了。

less文件左边出现了一个小箭头,点开看到了css文件,完全同步。

JS获得element的index

获得某个element在parent中的index。

jquery提供了index接口,可以直接拿到。

如果没有jquery的话,

将document查询到的HTML Collection转为Array,

然后使用Array的index接口拿到index。

var ItemList = Array.prototype.slice.call( document.getElementsByClassName("item"));

var _currentFocus = document.getElementsByClassName("item focus")[0];
var _position = ItemList.indexOf(_currentFocus);

CSS:position、float

之前写css,一直是把position:absolute当做android的FrameLayout用的,其他类似。

后来看了下面的文章,满面羞愧。

这些基础的东西,还是要了解透彻的,一直不求甚解的搞下去,自己都不知道做的什么,为什么会出现这种错误,希望能从下面的内容中,总结出一个类似于Androidlayout的规则。

关于position看下面的文章就够了

http://blog.csdn.net/chen_zw/article/details/8741365

css属性大全

http://css.doyoe.com/

这里主要记一下看完自己的理解:

文档流

首先需要了解什么是文档流,可以理解为方块布局,从上到下,从左到右的布局方式就是文档流。

1.margin和padding是占用文档流的,也就是说,layout函数一定是这么写的

element.margin + elment.width/height +element.padding = dom真正占用的空间。

position:static很容易理解,完全遵循文档流。

position:relative 有点特殊,本身遵循文档流,但可以在文档流中使用top left bottom right设置偏移,偏移不遵循文档流。需要注意的是,后面的dom会按照relative元素不偏移来排列。layout函数中完全可以把relative按照static先处理,最后在当前位置进行偏移。

注:relative static无父辈时以body为参考;

position:absolute 脱离文档流,使用top left bottom right设置偏移,偏移参照为父辈最近的非static元素(之前一直认为absolute参照window,深刻检讨)。

注:absolute无父辈时以html为参考。

1、body默认有9个px的margin

2、absolute的元素没有设置top、left时,默认是文档流的位置,会造成设置了absolute,看起来没有效果的现象。

position:fixed fixed以window为参考(即不管你怎么scroll,位置都不会变),设置偏移。

float:absolute会屏蔽掉float,其他的可以共存。

float不遵循文档流。

clear:clear是配合float使用的。意思是把dom的哪边给清除掉。

如clear:both,意思是两边都不许浮动。

如果我来写 html的layout,

1.dom tree static,relative的按照文档流布局,relative单独处理下偏移,完全不影响文档流。

fixed很简单粗暴。

2.处理absolute float

absolute屏蔽float

absolute需要寻找父辈非static元素直到html,比较蛋疼。这个webkit在渲染的时候会把absolute的元素单独出来一层。

float是浮动排版,按文档流布局就差不多。

当然,css3还有更多的排版方式,上面几种已经基本够用,如果还要学习的话就是box了。

JS-call apply bind

经常碰到JS中的这三兄弟,记一下他们的用法和区别。

1.改变函数上下文

2.就算不为了改变,但JS那坑爹的作用域下,也为了能够明确知道函数上下文

(为什么要改变请参考JS的作用域链)

区别:

call与apply

call与apply用法很相似,区别就是一个是一个一个传参数,一个是传一个参数数组

fun().call(object, p1, p2);

fun().apply(object, [p1,p2]);

或者经常直接继承父函数参数

fun().apply(object, arguments);

call和apply都会立刻执行,只是改变了fun()里面的this

bind不会立刻执行,bind会返回一个指定上下文的函数

var fun1 = fun().bind(object, p1, p2); //参数传递跟call一样

bind最适合作为回调函数使用,尤其是setTimeout

func(function(){}.bind(this));

我们知道,setTimeout的回调函数上下文会被置为window(use strict下是null),在setTimeout中没办法使用原有实例,bind就可以了

setTimeout(function(){}.bind(this), 1000);

JS-使用canvas绘制动画

HTML5中提供了transform transition等动画方式,已经能够满足绝大部分动画需求。

但在移动端,使用Transform等还是会出现不流畅的情况,比如背景上一个无限循环的动画,不管是使用setInterval 还是捕捉每次的AnimationEnd来实现,都会有一定的问题,因为我们设定的延时还是动画时间都不能得到保证,并且会影响页面性能。

优化是无尽的,所以学习使用Canvas

使用Canvas能做什么?

1.知道/控制每帧的绘制

2.预加载img来绘制

3.canvas保证了性能

如何使用Canvas?

http://www.w3school.com.cn/html5/html_5_canvas.asp

W3C等有canvas的简单介绍,使用Canvas的基本步骤就是

var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");

拿到canvas标签的dom,调用dom的getContext接口拿到Canvas类,就可以使用canvas的各种接口了

Canvas接口手册:http://www.w3school.com.cn/tags/html_ref_canvas.asp

canvas简单使用

1.绘制矩形

cxt.fillStyle="#FF0000";
cxt.fillRect(0,0,150,75);

2.绘制线

cxt.moveTo(10,10);
cxt.lineTo(150,50);
cxt.lineTo(10,50);
cxt.stroke();

3.绘制圆形

cxt.fillStyle="#FF0000";
cxt.beginPath();
cxt.arc(70,18,15,0,Math.PI*2,true);
cxt.closePath();
cxt.fill();

4.绘制渐变

var grd=cxt.createLinearGradient(0,0,175,50);
grd.addColorStop(0,"#FF0000");
grd.addColorStop(1,"#00FF00");
cxt.fillStyle=grd;
cxt.fillRect(0,0,175,50);

5.绘制图

var img=new Image()
img.src="flower.png"
cxt.drawImage(img,0,0);

这些绘制组合使用基本上可以满足我们的一般绘制需求。

绘制动画基本流程

But,如果要绘制动画,还需要配合另一个神接口,

requestAnimationFrame

requestAnimationFrame的用法请自行google。大概原理就是,requestAnimationFrame需要传入一个函数,浏览器每绘制一帧都会通过requestAnimationFrame来调用这个函数。通常,我们把这个函数命名为step,在step中,放入我们的draw函数(这里与Android的draw流程很类似,不过Android更方便些)。

draw与requestAnimationFrame绑定

this.show = function() {
    var step = function() {
        this.draw();
        this.animationId = requestAnimationFrame(step.bind(this));
    };
    this.animationId = requestAnimationFrame(step.bind(this));
    this.element.style.visibility = "visible";
};

解绑(这里把绑定与show/hide放到了一起)

this.hide = function() {
    cancelAnimationFrame(this.animationId);
    this.element && (this.element.style.visibility = "hidden");
};

draw函数

this.draw = function() {
    this.flash++;
    this.context.clearRect(0, 0, this.element.width, this.element.height);
    this.rect0.update();
    this.rect0.draw(this.context);
};

draw函数中我们把绘制分为两步,一步是update(和Android类比就是在这里做mesure,确定View中各个元素的位置),第二步是绘制。

子View中实现各自的update和draw

需要注意的是,Canvas的使用宽高默认为300 150,我们设定的宽高只会对Canvas进行缩放。

update-动画曲线

使用css动画时,可以方便的给一个动画曲线,比如easeinout等,使用canvas绘制就需要我们不断的update绘制范围。

this.update = function() {
    if (!this.isAnimating) {
        return;
    }
    this.t += 0.05;
    switch (this.direction) {
        case "right":
            this.x = easeInOutQuint(this.t) * 70;
            break;
        case "down":
            this.y = easeInOutQuint(this.t) * 70;
            break;
        case "left":
            this.x = (1 - easeInOutQuint(this.t)) * 70;
            break;
        case "up":
            this.y = (1 - easeInOutQuint(this.t)) * 70;
            break;
    }
    if (this.t >= 1) {
        this.nextDirection();
        this.t = 0;
    }
};

在update里面,我们完成让一个矩形以指定动画曲线在四个方向转圈的位移。

缓动函数就负责返回位移的值

下面的easeInOutQuint按照步进返回值

var easeInOutQuint = function (t) {
    return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t;
};

更高明的做法,比如Jquery中,以时间为参数

function easeOutBounce(progress, currentTime, begin, change, duration) {
    if ((currentTime/=duration) < (1/2.75)) {
        return change*(7.5625*currentTime*currentTime) + begin;
    } else if (currentTime < (2/2.75)) {
        return change*(7.5625*(currentTime-=(1.5/2.75))*currentTime + .75) + begin;
    } else if (currentTime < (2.5/2.75)) {
        return change*(7.5625*(currentTime-=(2.25/2.75))*currentTime + .9375) + begin;
    } else {
        return change*(7.5625*(currentTime-=(2.625/2.75))*currentTime + .984375) + begin;
    }
}

draw就比较简单了

this.draw = function(ctx) {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, 70, 70);
};

什么是Web-Worker?

WebWorker类似于浏览器中的多线程操作。之前的JS中,无论你是使用setTimeout setIntever 还是 使用了XMLHttpRequest,都是在一个线程里面,前两个使用消息队列,XMLHttpRequest则是浏览器会帮你进行闲时进行,归根结底,都是在一个线程里面跑。如果用户想进行一些阻塞操作,很可能会产生卡住页面的情况。甚至于我们想实现一个类似于Android的专用 公用Service,那该怎么办?

H5新标准提出了WebWorker的概念,各个浏览器都各自实现了,先来看一下WebWorker能做什么。

 

WebWorker特点,在后台线程执行JS的能力,与页面通过send message这种方式通信。

WebWorker有两种,专用worker(dedicatedworker)与公用worker(sharedworker)。

疑问:worker与主线程如何同步,worker与主线程同时操作了一个DOM元素,会不会产生脏数据?所以,worker的能力被加以限制,不能访问DOM元素。

worker在chrome中是如何实现的?

chrome浏览器是多进程架构,分为Browser进程以及Render进程,每打开一个页面,浏览器都会为其分配一个Render进程,webkit以及js都运行在这个进程内。如果要起一个DedicatedWorker,Chrome会在Render进程中起一个线程。如果要起一个SharedWorker就稍微复杂一点,必须起一个专门的进程,并且,相同的SharedWorker不管你创建多少次,都只存在一个。

Android中的Chrome有一个限制,限定9个进程。

1.如果用户创建太多SharedWorker,可能第二个标签页都打不开?

2.SharedWorker的优先级如何定义?如果使用SharedWorker的页面都在后台,其优先级如何?

目前Android上的SharedWorker还处于讨论阶段,未实现。

下面看一下worker的基本用法,DedicatedWorker:

worker.js

this.addEventListener('message', function(e) {
    var data = e.data;
    console.log("worker: " + data);
    this.postMessage(data + 1);
});

worker中需要一个this.onmessage接收消息

postMessage发送消息

参数在 event.data中

main.js

if (window.Worker) {
    var worker = new Worker("./worker.js");
    worker.onmessage = function(e) {
        document.getElementById("worker").innerHTML = e.data;
    };

    document.addEventListener('keydown', function(evt) {
        worker.postMessage(evt.keyCode);
    });
}

使用Worker这个API来创建DedicatedWorker。

同样通过worker实例的onmessage和postMessage通信。

结束Worker:

在worker中,可以通过close()来kill掉自己

main中则调用worker.terminate()

如果worker运行中出现错误,在main中使用worker.onerror可以接收到错误消息

SharedWorker:

https://github.com/mdn/simple-shared-worker

SharedWorker跟DedicatedWorker有两个不同的地方:

1.通信不再直接通过worker,而是worker的port类

2.worker中需要实现onconnect,且其参数中有一个port列表,但目前只使用到了第一个

sharedworker.js

onconnect = function(e) {
   var port = e.ports[0];

   port.onmessage = function(e) {
     var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
     port.postMessage(workerResult);
   }

}

main.js

if (!!window.SharedWorker) {
  var myWorker = new SharedWorker("worker.js");

    myWorker.port.postMessage([squareNumber.value,squareNumber.value]);

  myWorker.port.onmessage = function(e) {
    result2.textContent = e.data;
    console.log('Message received from worker');
  }
}

当然 还可以写另一个页面,同样可以使用跟main.js类似的方法跟sharedworker通信。

ServiceWorker:

ServiceWorker是WebWorker的一种,它更加复杂,所以也更加强大。

首先,ServiceWorker有独立的生命周期:

1.Register:主线程调用API注册ServiceWorker

2.Installing:浏览器启动安装过程,加载和缓存一些静态资源

有可能失败进入Error状态

3.Activated:激活阶段,此阶段可以升级ServiceWorker

4.激活后,ServiceWorker会接管页面,如果页面是刚刚注册,本次不会被接管,下次加载页面才会接管。

5.ServiceWorker接管页面后,如果有fetch和message事件,会处于onfetch和onmessage,其他情况可能被终止。

ServiceWorker的特性:

1.它是一个worker,同样不能操作dom元素,同样可以通过postMessage与调用线程通信

2.ServiceWorker增加了网络处理,onfetch

3.ServiceWorker不被使用的时候,它会自己终止,再次使用会被重新激活,不要依靠它的内存来保存信息,请用webStorage或者indexDB。

4.ServiceWorker大量使用Promise,就是封装的一个callback标准

5.ServiceWorker权限很大,即所有网络请求都经过它,可以劫持连接,伪造和过滤响应,所以只能在https网页上注册ServiceWorker(是只能么?)。

6.ServiceWorker只作用于同域的fetch。onfetch有一个缓存的例子,我们通过缓存类缓存一些request和response,下次request,直接去缓存找response,处理失败了再去网络实时请求。注:response的类型需要是basic,即同域请求。

8.ServiceWorker的自动更新。

当网页激活时,浏览器会检查ServiceWorker是否有更新(有一个字节不同就会认为有更新),浏览器后台下载。

下载完开始运行,进入install状态,之后进入waitting状态。因为此时旧的ServiceWorker仍然在运行。

当页面被杀掉,旧去新来。

当然,如果你之前缓存了request,更新后需要清理一下。

现有问题:

如果在Install时失败了,页面无法感知。

ServiceWorker主要作用是在onfetch里面缓存/处理request。

https://github.com/GoogleChrome/samples

中有大量的onfetch与cache结合使用做离线应用的例子,在此不多赘述。

来自

http://blog.csdn.net/yl02520/article/details/14446763

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

JS处理相对路径

遇到引擎不能处理相对路径的问题,写了一个简单的相对路径转绝对路径的函数,留下备用:

function rel2Abs(path) {
    var _path = path;
    var locationStr = location.href.toString();
    console.log(locationStr);
    var result = '';

    var relative;
    while (relative = _path.substring(0, _path.indexOf("\/"))) {
        console.log(relative);
        if (relative === '.') {
            // 遇到. path去掉最前面的./
            // 遇到.当前路径去掉/后面内容
            _path = _path.substring(_path.indexOf('\/') + 1, _path.length);
            locationStr = locationStr.substring(0, locationStr.lastIndexOf('\/') + 1);
        } else if (relative === '..') {
            // 遇到.. path去掉最前面的../
            // 遇到.. 当前路径去掉/xxx,然后再去掉/后面的内容
            _path = _path.substring(_path.indexOf('\/') + 1, _path.length);
            locationStr = locationStr.substring(0, locationStr.lastIndexOf('\/'));
            locationStr = locationStr.substring(0, locationStr.lastIndexOf('\/') + 1);
        } else {
            break;
        }
    }
    var result = locationStr + _path;
    path = result;
    return result;
}

原理很简单,只支持/,且遇到错误不能自动处理