NiYingfeng 的博客

记录技术、生活与思考

0%

第一部分 编程风格

每个人都有自己独特的编程风格,有情有独钟亦或恨之入骨的,每个人都希望用自己的方式制定规范来实施。这些应当被归为个人编程嗜好。我们需要的是在编程过程中尽早的确定统一的编程风格。 在团队开发中,编程风格一致性变得尤为重要,因为: 1 任何开发者都不在乎某个文件的作者是谁,也没有必要花费额外的精力去理解代码的逻辑并且重新排版,因为所有代码的排版风格都是非常一致的,因为风格不一致会导致我们打开代码文件的第一件事情不是立即工作,而是进行缩进的排版整理。 2 能很容易的辨别出有问题的代码并且发现错误,如果所有的代码风格很像,当看到一段与众不同的代码时,很肯能问题就出在这里。

JSLint 和 JSHint 是两个检查编程风格的工作。不仅找出代码中潜在的错误,还能对潜在的风格问题给予提示警告。 “程序是写给人读的,只是偶尔让计算机执行一下” - Donald Knuth

第一章 基本的格式化

编程风格的核心就是基本的格式化规范,这些规范决定着如何编写高水准的代码。

1.1 缩进层级

所有语言都是如此,都会讨论如何对代码缩进。

1
2
3
4
5
6
7
if(wl && wl.length){
for(var i=0; i<wl.length;i++){
p = wl[i];
type=Y.Lang.type(wl[i]);
...
}
}

稍微编写过程序的都不会使用上面的格式,害人害己。

1
2
3
4
5
6
7
if(wl && wl.length){
for(var i=0; i<wl.length;i++){
p = wl[i];
type=Y.Lang.type(wl[i]);
...
}
}

代码应该如何统一缩进其实一直没有统一的共识。

  • 使用制表符缩进 。
  • 使用空格缩进。

jQuery 明确规定使用制表符缩进。 Dauglas Crockford 的 JavaScript 规定使用4个空格缩进。 Google 的 JavaScript规定使用2个空格缩进。 Dojo 编程风格指南使用制表符缩进。

尼古拉斯(作者)建议4个空格,或在编辑器中设置一个 制表符 替换为 4个空格。为一个缩进层。

1.2 语句结尾

强制规定,语句结束插入分号。

1
2
3
4
5
6
function(){
return
{
...
}
}

上面代码就会引起问题。

1.3 行的长度

很少有JS的规范提及到行的长度的风格的,很多语言都建议行的字数不超过 80 字,因为很早以前的编辑器,这是最大的行长度。

Java语言规范中 源码单行长不超过 70 字,文档中单行长不超过 80 字。 Android开发者规范中规定不超过 100 字。 非官方的Ruby规定不操过 80 字。 Python编程规范规定单行不超过 79 字。

JavaScript 在 Crockford 代码规范中规定为 80 字符。

1.4 换行

当一行字字符数超过了最大限制的时候就需要换行。通常会在运算符之后换行,下一行会增加2个层级的缩进。

1
2
callFunction(document,"aaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbb","cccccccccccccccccccc",
"bbbbbbbbbbbbbbbbbbb");//超出首字母c的2个层级

需要注意的是规范为在运算符之后换行,以及增加2个层级的缩进。 当然如果是赋值的话就保持赋值运算符对其。

1
2
var result = "aaaaaaaaaaaaaaaa" + "bbbbbbbbbbbbbbbb" + "ccccccccccccccccccc" +
"ddddddddddddddddddddddddd";

1.5 空行

空行常常被忽略,但是空行也是提高代码可读性的强大武器。

1
2
3
4
5
6
7
8
9
10
11
12
if(wl && wl.length){

for(var i=0; i

p = wl[i];
type=Y.Lang.type(wl[i]);

if(a){
...
}
}
}

空行一般来说不是很好在规范中规定,

  • 方法之间
  • 方法中局部变量可第一条语句之间。
  • 多行或者单行注释之前。
  • 在方法内的逻辑片段之前。

目前没有规范对空行进行明确的规定。

1.6 命名

“计算机科学只存在两个难题:缓存失效和命名” - Phil Karlton

只要写代码就会涉及到变量和函数,所以变量和函数的命名对于增强代码的可读性至关重要。 JavaScripe 遵循 ECMA ,即驼峰式大小写。

1
2
var myName;
var anotherName;

google Dojo等规范都规定使用小驼峰命名法。,当然还有一种曾经风光一时的 匈牙利命名法。 1.6.1 变量和函数 变量名总是应该遵循驼峰大小写命名法,并且命名的前缀应当是名词,使得可以将变量和函数区分开。

1
2
3
4
// 以下OK
var count = 10;
var myName = "Ni";
var found = true;

// 以下就需要重新命名

1
2
var getCount = 10;
var isFound = true;

//函数应当以动词开头

1
2
3
var getName  = function(){
return myName;
};

还有一点示范

can 函数返回布尔值 has 函数返回布尔值 is 函数返回布尔值 get 函数返回非布尔值 set 函数用来保存值

1.6.2 常量 JS中没有常量的概念。来自C语言的约定,常量使用大写字母并且使用下划线命名。

1
2
var MAX_COUNT = 10;
var URL = "http://www.guokr.com";

google Dojo 等编程风格指南都规定如此。 1.6.3 构造函数 JS中的构造函数只不过是冠以 new 运算符的函数而已。采用大驼峰命名法。

1
2
3
function Person( name ){
this.name = name;
}

1.7 直接量

JS中的原始值:字符串、数字、布尔值、null和underfined。 1.7.1 字符串 JS中的字符串使用双引号和单引号是完全相同的。对于规范只需要统一即可。 jQuery 和 Crockford规范为双引号来括住字符串,google规范为单引号括住字符串。 1.7.2 数字 JS中整数和浮点数都存储为相同的数据类型。 不推荐使用 10. 和 .1 的写法。会产生歧义,是否是我们漏掉了数字。

1.7.3 null null 常会与 undefined 搞混淆。以下场景才应当使用null:

  • 用来初始化变量,该变量可能被赋值为对象。
  • 用来和一个已经初始化的变量进行比较,这个变量可以是非对象。
  • 当函数期望的参数是对象,用作参数传入。
  • 当函数的返回值期望是对象时,用作返回值传出。

以下场景不应当使用 null:

  • 不要使用null来检测是否传入了参数。
  • 不要用null来检测一个未初始化的变量。
1
2
3
4
5
6
7
8
var person = null;
function getPerson(){
if( condition ){
return new Person("ABC");
}else{
return null;
}
}

对于 null 的理解最好是当做对象的占位符使用,主流的规范没有提及。 1.7.4 undefined 由于

1
2
3
var person;
typeof person; // undefined
typeof foo; // undefined

以及null == undefined所以,禁止使用特殊值 undefined 可以有效地确保只在一种情况下 typeof 才返回 undefined。 1.7.5 对象直接量 与 数组直接量(字面量) 创建对象与数组最流行的方法为对象字面量和数组字面量,比相应的构造函数式创建更高效直接。

第二章 注释

2.1 单行注释

三种使用方法:

  • 独占一行,用来解释下一行,并且这行注释之前总有一行空行,缩进的层级与下一行代码保持一致。
  • 在代码行的尾部注释。代码与注释之间至少有一个缩进。注释包括之前本行的代码不应该超过最大字符数限制。
  • 注释掉大块代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 好的注释方法
if( condition ){

// 解释下一行代码
func();
}

// 不好的注释方法
if( condition ){
// 解释下一行代码
func();
}

if( condition ){
// 解释下一行代码
func();
}

2.2 多行注释

偏向使用 Java 风格的注释

1
2
3
4
5
/*
* 这里是注释
* 这里是注释
* 这里是注释
*/

缩进的格式与单行注释表示一致。

2.3 使用注释

当代码不够清晰的时候使用注释,当代码很明了的时候不应当添加注释。 并且注释不应该只是解释变量名称或者函数名称式的那种废话。 当代码很难理解或者代码可能被误认为错误的代码的时候,需要添加注释。

2.4 文档注释

技术角度来说,文档注释并不是 JS 的组成部分,但是是一种普遍的实践。

  • 应当对所有的方法和可能的返回值添加描述。
  • 对自定义的构造函数类型和期望的参数添加描述。
  • 对于包含对象或者方法的对象进行描述。

第三章 语句和表达式

JS中,诸如 if 和 for 之类的规定不管是单行代码还是多行代码,均使用 { }

1
2
3
4
5
6
7
8
9
10
11
12
13
// 好的写法
if( contidion ){
foo();
}

if( conidion ){
foo();
foo();
}

// 不好的写法
if( contidion ){ foo(); }
if( contidion ) foo();

所有的语句块都应当使用 { }, if , for , while ,do while , try catch finnally 。

3.1 花括号对齐方式

有两种对齐方式

1
2
3
4
if( conidion ){
foo();
foo();
}

1
2
3
4
5
if( conidion )
{
foo();
foo();
}

很显然由于第二种又时候会会让浏览器执行代码的时候产生不一样的解析,所以使用第一种花括号对齐方式。

3.2 块语句间隔

语句块间隔有3中形式:

  • 语句名 圆括号 左花括号间没有空格间隔。
  • 左圆括号之前 右圆括号之后添加一个空格。
  • 左圆括号 右圆括号前后都添加一个空格。

作者推荐一致使用第二种形式。

3.3 switch 语句

JS中任何表达式都可以合法的用于case从句中,其他语言必须使用原始值和常量。 3.3.1 缩进 JS的switch缩进一致是有争议的话题。

1
2
3
4
5
6
7
8
9
10
11
12
switch (condition) {
case "a":
//代码
break;

case "b":
//代码
break;

default :
// 代码
}

每个case相对于switch都有一个缩进层。 第二个case开始之前都有一个空行。 另一种 Crockford 和Dojo编程风格为

1
2
3
4
5
6
7
8
9
10
switch (condition) {
case "a":
//代码
break;
case "b":
//代码
break;
default :
// 代码
}

3.3.2 case语句的连续执行(贯穿)

1
2
3
4
5
6
7
8
9
switch (condition) {
case "a":
case "b":
//代码
break;

default :
// 代码
}

Douglas 在 Crockford中禁止出现贯穿现象, 但是Nichlas认为只要程序逻辑很清楚,贯穿完全是一个可以接受的编程方式。Jquery 编程风格中也是允许的。 3.3.3 default 是否在任何时候都需要 default。

1
2
3
4
5
6
7
8
switch (condition) {
case "a":
case "b":
//代码
break;

//没有 default :
}

更倾向于没有默认行为并且在注释中写入省略 default。

3.4 with

with 语句可以更改包含上下文解析变量的方式。通过with可以使用局部变量的方式和函数的形式来访问特定对象中的属性和方法。可以缩短代码的长度。 但是是的JS引擎与压缩工具无法压缩。严格模式中 with 是禁止的。 基本所有的规范中都禁止使用 with。

3.5 for 循环

对于for循环中的 break 和 return 会改变循环的方向,尽量避免使用,但是不禁止。

3.6 for-in 循环

for-in循环建议与 hasOwnProperty 一起检测属性,除非你需要检查原型。 for-in是用来遍历对象的,不要用其来遍历数组。

canvas

canvas 其实对于HTML来说很简单,只是一个标签元素而已,自己并没有行为,但却把一个绘图 API 展现给客户端 JavaScript 以使脚本能够把想绘制的东西都绘制到一块画布上,拥有绘制路径,矩形,圆,字符以及图像等功能。 所有的标签只是图形的容器,必须使用JavaScript的 API 操作绘图。

canvas 浏览器支持性

canvas 绘图

首先是需要在页面中有 canvas 这个图形容器,为了方便在浏览器测试,直接控制台覆盖写入 canvas 标签页面、

1
2
3
document.open();
document.write();// 这边代码无法显示在编辑器下 插入一个canvas标签
document.close();

接下来是使用接口获取并渲染为 2d 对象(目前只支持二维)。

1
2
var canvas = document.getElementById("canvas");
var cantxt = canvas.getContext("2d");

首先是获取到 canvas 对象,从 canvas 对象中得到二维对象进行处理。 如果使用 Object.prototype.toString 来检测 cantxt 的话, 类型为 [object CanvasRenderingContext2D] 获取了其 2d 的对象 便可以操作 canvas 了

首先是一些路径的 API:

描绘路径的方法: moveTo(x , y) : 显示的指定路径的起点为 x , y,左上角为原点,横向为 X 轴,纵向为 Y 轴。canvas 默认起点为 0 , 0。 lineTo(x , y) : 描绘一条从起点到 (x , y) 点的直线,并且将起始位置设为 (x , y)。 rect(left , top , width , height) : 描绘一个已知左上角端点位置,以及高和宽的矩形。描绘完成后起点会一定到左上角位子。 arcTo( x1 , y1 , x2 , y2 , radius ) : 用于描绘一个与两条线段相切的圆弧,两条线段分别以当前Context绘制起点和(x2, y2)点为起点,都以(x1, y1)点为终点,圆弧的半径为radius。描绘完成后绘制起点会移动到以(x2, y2)为起点的线段与圆弧的切点。 arc( x , y , radius , startAngle , endAngle , anticlockwise ) : 用于描绘一个以(x, y)点为圆心,radius为半径,startAngle为起始弧度,endAngle为终止弧度的圆弧。anticlockwise为布尔型的参数,true表示逆时针,false表示顺时针。参数中的两个弧度以0表示0°,位置在3点钟方向;Math.PI值表示180°,位置在9点钟方向。 quadraticCurveTo( cpx , cpy , x , y) : 以当前Context绘制起点为起点,(cpx,cpy)点为控制点,(x, y)点为终点的二次样条曲线路径。 bezierCurveTo( cpx1 , cpy1 , cpx2 , cpy2 , x , y); : 以当前Context绘制起点为起点,(cpx1,cpy1)点和(cpx2, cpy2)点为两个控制点,(x, y)点为终点的贝塞尔曲线路径。 在描绘完路径之后,还需要使用以下方法 绘制路径或者填充颜色: stroke() : 按照路线绘线条。 fill() : 使用当前设置好的 style 来填充路径区域。 clip() : 按照已有的路线在画布中设置剪辑区域,调用后图形编辑代码只会对编辑区域有效,对外界无效。如未调用则就是当前整个 canvas 为编辑区域。 e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cantxt.rect(50,50,40,80);
cantxt.fillStyle = "#0F0";
cantxt.fill();

cantxt .moveTo(50,50); // 移动到坐标 50 50
cantxt.lineTo(150,150); // 划出轨迹到 150 150
cantxt.stroke(); // 以线条显示轨迹

// 突然会发现,颜色方面有点错误,线条的黑色会影响到矩形的绿色。所以需要使用 打开路径 关闭路径的方法隔断路径间的联系。

cantxt.beginPath();
cantxt.rect(50,50,50,100);
cantxt.fillStyle = "#0F0";
cantxt.fill();
cantxt.closePath();

cantxt.beginPath();
cantxt .moveTo(50,50); // 移动到坐标 50 50
cantxt.lineTo(150,150); // 划出轨迹到 150 150
cantxt.stroke(); // 以线条显示轨迹
cantxt.closePath();

cantxt.beginPath();
cantxt.moveTo(50,50);
cantxt.arcTo(100,100,200,50,50);
cantxt.stroke();
cantxt.closePath();

以及其他的几个方法: fillText(text , left , top , [maxWidth]) : 字符串,相对与原点的坐标,字符串的最大长度。其中最大长度maxWidth是可选参数。

1
cantxt.fillText("abcde",100,300);

还有一种方式是不使用路径,直接填上颜色: fillRect( left , top , width , height ) : 直接填充矩形。 strokeRect( left , top , width , height ) : 划出矩形边框。 clearReck(left , top , width , height ) : 清除矩形内所有内容。

1
2
3
cantxt.strokeRect( 50,50,100,100 );
cantxt.clearRect( 50,50,100,100 ); // 发现还会留下一圈边框
cantxt.clearRect( 49,49,102,102 );

fillRect没有上面的问题。 还有几个可以设置的属性: strokeStyle : 线条颜色,默认为 “#000000”,可设为 css颜色值,渐变对象,或者模式对象。 fillStyle : 填充的颜色。 lineWidth : 线条宽度。单位 px。 lineCap : 线条端点样式, butt 无,round 圆头, square 方头。 lineJoin : 线条转折的处理,round 圆角 , bevel 平角 , miter 尖角。 miterLimit : 尖角的锐利程度,默认 10. translate(x,y):平移变换,原点移动到坐标(x,y); rotate(a):旋转变换,旋转a度角; scale(x,y):伸缩变换; save(),restore():提供和一个堆栈,保存和恢复绘图状态,save将当前绘图状态压入堆栈,restore出栈,恢复绘图状态;

1
2
3
4
5
cantxt.translate(200,200);
cantxt.rotate(1);
cantxt.moveTo(0,0);
cantxt.lineTo(100,100);
cantxt.stroke();

Context对象中拥有drawImage()方法可以将外部图片绘制到Canvas中。 drawImage()方法的3种原型如下: drawImage(image, dx, dy); drawImage(image, dx, dy,dw, dh); drawImage(image, sx, sy,sw, sh, dx, dy, dw, dh); 其中,image参数可以是HTMLImageElement、HTMLCanvasElement或者HTMLVideoElement。第三个方法原型中的sx、sy在前两个中均为0,sw、sh均为image本身的宽和高;第二和第三个原型中的dw、dh在第一个中也均为image本身的宽和高。

backbone 简介

当我们在开发大量 JavaScript 的 web 应用的时候,很大的工作就是处理 DOM 以及 DOM 中数据的问题。如果没有很好的组装架构,那么就会带来混乱的结构与越来越复杂和麻烦的问题。 backbone 是一个重量级的 JavaScript 应用框架。 为复杂的 JavaScript 应用程序提供了模型(models)、集合(collections)、视图(views)的结构。

  • 模型:用于绑定键值数据和自定义事件
  • 集合:附加有可枚举函数的丰富API
  • 视图:声明事件处理函数,通过RESRful JSON接口连接到应用程序。

backbone.js 对于 Underscore.js 是重度依赖,对于RESTful,history则依赖与Backbone.Router,DOM处理依赖于 Backbone.View,json2.js,Jquery或者Zepto。 Backbone 将数据呈现为模型,可以创建模型、验证模型、销毁模型和保存模型至服务器。当 UI 的改变引起模型属性变化的时候就会触发 “change” 事件,所显示模型数据的 视图 会接受到更改通知,重新渲染。

backbone 使用

backbone.Events backbone 中的 event 模块可以被拓展到任意对象,使任何对象都可以使用自定义事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
```
var Events = Backbone.Events = {
// 对一个事件绑定回调函数. 传递 "all" 则将绑定回调在所有事件触发时调用
on: function(name, callback, context) {
// API 检测
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
//相当于 this._events = this._events || {} 下面语法更容易用来赋值 如下下句
//在绑定时间的对象中 建立事件池 来进行事件管理
this._events || (this._events = {});
// name 事件在事件池中的形式(数组形式) 存放当前对象绑定在name的所有事件
var events = this._events[name] || (this._events[name] = []);
// 将当前需要绑定的事件 push到事件池中的具体事件名称中
events.push({callback: callback, context: context, ctx: context || this});
return this;
},
off: function(){},
once: function(){},
trigger: function(){},
stopListening: function(){}
}
1
2
3

其实以上方法和 Jquery 的实现类似,只不过更为简单,Jquery 中还对当前需要绑定事件的对象如果为 DOM 元素的话需要进一步处理来保证 IE 中内存遇到的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 解绑一个或者多个回调.
// 如果 `context` 是 null, 移除所有有该函数的回调.
// 如果 `callback` 是 null, 移除该事件下的所有回调.
// 如果 `name` 是 null, 移除所有帮定的回调函数
off: function(name, callback, context) {
var retain, ev, events, names, i, l, j, k;
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;

// obj.off() 移除所有事件
if (!name && !callback && !context) {
this._events = {};
return this;
}

// 使用 underscore 的 获取对象键值方法
names = name ? [name] : _.keys(this._events);
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
// 移除某一事件下的回调
if (events = this._events[name]) {
this._events[name] = retain = [];
if (callback || context) {
for (j = 0, k = events.length; j < k; j++) {
ev = events[j];
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
(context && context !== ev.context)) {
// 不是所需要删除的事件 则重新扔回 事件池中的对应名称中
retain.push(ev);
}
}
}
//如果对应的名称中没有事件,那么删除事件池对象的该属性
if (!retain.length) delete this._events[name];
}
}
return this;
}
1
2
3

事件移除,简单地说因为事件绑定是使用了 事件池 的方法处理,那么对于移除事件只需要在事件池中 delete 掉响应的事件即可,唯一需要做的是兼容参数个数不同情况下的移除事件情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 触发一个或多个事件, 触发所有绑定的回调.
// 除了事件名称,回调函数会被传递'trigger'相同的参数。
// (如果你监听了 'all', 会让你的回调函数将事件名称作为第一个参数).
// obj.trigger("change",function(){});
// obj.trigger("all",function(eventName){ alert(eventName) });
trigger: function(name) {
if (!this._events) return this;
var args = slice.call(arguments, 1);
if (!eventsApi(this, 'trigger', name, args)) return this;
var events = this._events[name];
//别忘了任何事件触发都要调用 all 中的事件
var allEvents = this._events.all;
if (events) triggerEvents(events, args);
if (allEvents) triggerEvents(allEvents, arguments);
return this;
},

// 使这个对象或者所有监听特定事件的对象停止监听该特定的事件
stopListening: function(obj, name, callback) {
var listeners = this._listeners;
if (!listeners) return this;
var deleteListener = !name && !callback;
if (typeof name === 'object') callback = this;
if (obj) (listeners = {})[obj._listenerId] = obj;
for (var id in listeners) {
listeners[id].off(name, callback, this);
if (deleteListener) delete this._listeners[id];
}
return this;
}
1
2
3

触发与停止监听。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 分割事件字符串.
var eventSplitter = /s+/;
// 实现多样式化的事件功能的API
//比如现有API中的多名称的change blur和jquery风格的事件映射 {change: action}
var eventsApi = function(obj, action, name, rest) {
if (!name) return true;
// 处理事件映射
if (typeof name === 'object') {
for (var key in name) {
obj[action].apply(obj, [key, name[key]].concat(rest));
}
return false;
}
// 处理空间分割的事件名称.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, l = names.length; i < l; i++) {
obj[action].apply(obj, [names[i]].concat(rest));
}
return false;
}
return true;
};
// A difficult-to-believe, 在触发事件时优化内部调用. 尽可能快速到达最有可能的情况
// (核心的3个 Backbone 事件参数).
var triggerEvents = function(events, args) {
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
switch (args.length) {
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
}
};
var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
// 使用listenTo 和 listenToOnce反转 on and once 控制. 将 *this* 对象监听另外一个对象中的事件
// 保持对监听的跟踪
_.each(listenMethods, function(implementation, method) {
Events[method] = function(obj, name, callback) {
var listeners = this._listeners || (this._listeners = {});
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
listeners[id] = obj;
if (typeof name === 'object') callback = this;
obj[implementation](name, callback, this);
return this;
};
});
// 向后兼容的名称.
Events.bind = Events.on;
Events.unbind = Events.off;
1
2
3

使用的实例为

1
2
3
4
5
6
7
8
9
10
11
var object = {};
_.extend(object, Backbone.Events);
object.bind("alert", function(msg) {
alert("Triggered " + msg);
});
object.trigger("alert", "www.csser.com");


object.unbind("change", onChange); // 只移除onChange回调函数
object.unbind("change"); // 移除所有 "change" 回调函数
object.unbind();
1
2
3
4
5
6
7
8
9

`backbone.sync` _backbone.sync( method, model, [option] )_

* method - CRUD 方法 ("create", "read", "update", 或 "delete")
* model - 要被保存的模型(或要被读取的集合)
* options - 成功和失败的回调函数,以及所有 jQuery 请求支持的选项

是对于模型在服务器中读取和保存的函数,都为 json 格式进行数据传输。默认情况下是依赖于 jQuery或者Zepto 的 Ajax 方法进行的发送 RESTful json 请求。(关于 REST Representational State Transfer 表现层状态转化 可见阮一峰老师的 理解RESTful架构)。额~反正本人不是很了解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// Backbone.sync 同步
// -------------
// 重载这个方法来改变 Backbone 持久模型在服务器中的方式、
// 通过请求类型和问题中的模型
// 默认情况发送 RESTful Ajax 请求到模型中的`url()`.
// 一些可行的自定义为:
//
// * 使用 `setTimeout` 在一个请求中批量的触发更新.
// * 发送 XML 而不是 JSON.
// * 坚持模型通过 WebSockets 而不是 Ajax.
//
// 开启 `Backbone.emulateHTTP` 为像 `POST` 一样发送 `PUT` 和 `DELETE` 请求
// `_method` 参数中包含真正的 HTTP 方法,
// 以及主体的所有请求 as `application/x-www-form-urlencoded`
// 替换为 `application/json`参数名为 `model`的模块.
// 当接口为服务器端语言,如**PHP**时很有用, 使得主体的'PUT'请求难以读取
Backbone.sync = function(method, model, options) {
var type = methodMap[method];

// 默认设置, 除非指定.
_.defaults(options || (options = {}), {
emulateHTTP: Backbone.emulateHTTP,
emulateJSON: Backbone.emulateJSON
});

// 默认 JSON-request 设置.
var params = {type: type, dataType: 'json'};

// 确保有一个 URL.
if (!options.url) {
params.url = _.result(model, 'url') || urlError();
}

// 确保我们有正确的请求数据.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
params.contentType = 'application/json';
params.data = JSON.stringify(options.attrs || model.toJSON(options));
}

// 对于老的服务器, 模拟JSON以HTML形式的.
if (options.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.data = params.data ? {model: params.data} : {};
}

// 对于老的服务器, 模拟 HTTP 通过用 `_method` 方法仿造 HTTP
// 和一个 `X-HTTP-Method-Override` 头.
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
params.type = 'POST';
if (options.emulateJSON) params.data._method = type;
var beforeSend = options.beforeSend;
options.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
if (beforeSend) return beforeSend.apply(this, arguments);
};
}

// 在 non-GET 请求中不传递数据.
if (params.type !== 'GET' && !options.emulateJSON) {
params.processData = false;
}

// 如果我们发送 `PATCH` 请求,
// 我们在一个老版本ActiveX默认启动的情况下,使用XHR来取代jQuery方法。
// 删除它当IE8支持 `PATCH` 的时候.
if (params.type === 'PATCH' && window.ActiveXObject &&
!(window.external && window.external.msActiveXFilteringEnabled)) {
params.xhr = function() {
return new ActiveXObject("Microsoft.XMLHTTP");
};
}

//提出请求, 允许用户自定义Ajax选项.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
};

// 映射 CRUD 到 HTTP 为了默认的 `Backbone.sync` 执行.
var methodMap = {
'create': 'POST',
'update': 'PUT',
'patch': 'PATCH',
'delete': 'DELETE',
'read': 'GET'
};

// 通过 `$` 代理来设置 `Backbone.ajax` 的默认执行.
// 如果想要使用另一个库那么重载它.
Backbone.ajax = function() {
return Backbone.$.ajax.apply(Backbone.$, arguments);
};

```

backbone 源码注释

以上为 bockbone 的2个核心的模块,另外模块将在下两篇中简单介绍。 由于源码过长,请 查看 Github 猛击这里 。这里只为对 backbone 原文注释的简单翻译,具体还需要研究。 对于 backbone 的中文文档,请 点击这里 这是一个不错的中文文档,各个接口均有详细说明。

什么是执行上下文

执行上下文是 ECMAScript 中最常提到的概念。 规范上是这么解释的:

When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context. 当控制器转入到ECMAScript可执行代码时,控制器就会进入一个执行上下文。激活的执行上下文形成一个逻辑上的堆栈。在这个逻辑堆栈的最上层的执行上下文就是目前运行中的执行上下文。

e.g.1

1
2
3
4
5
6
console.log(func);
function func(){}
var func;
console.log(func);
func = "abc";
console.log(func);

是的,他和执行上下文有关。 e.g.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function show_arguments(a,b,c){
console.log(a);
console.log(b);
console.log(c);

console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);

arguments[0] = 0;
arguments[1] = 1;
arguments[2] = 2;

console.log(a);
console.log(b);
console.log(c);
}

show_arguments("a","b")

是的,也和执行上下文有关。 e.g.3

1
2
3
4
5
6
7
8
9
10
11
12
var name = "I am window";
function show_name(){
console.log(this.name);
}
var obj_test={
name:"I am obj_test",
wait_show:function(){
setTimeout(show_name,100);
}
}
show_name();
obj_test.wait_show();

这是一个之前在 this 中的例子,当然也与执行上下文有关。 执行上下文中东西非常的多。简单来说,执行上下文是有个抽象的对象,可以想象他是一块隐藏在JS控制器里面的内存,用来保存JS中的各种数据以便JS代码中使用这些数据,保证可以代码顺利执行。

执行上下文与其他 某某某 的关系

具体来说,一个执行上下文中包含了3部分, 变量对象,作用域链,this值。 这是一张很好的对执行上下文说明的图片,变量对象,作用域链,this值以及他们本身所包含的数据。可以理解为 EC在JS作用扮演着对JS代码执行控制的角色,其他所有的 变量啊 属性啊 函数啊都是隶属于他旗下的,由他进行把控。

执行上下文是什么时候产生的

不同的函数为什么会有不同的作用域,其本质就是因为他们在不同的执行上下文中,执行上下文当然不只是一个,当控制器转入到一段ECMA可执行代码的时候就会进入一个新的执行上下文。 e.g.4

1
2
3
4
5
6
7
8
function a(){
...//代码
function b(){
...//代码
}
b();
}
a();

形式上比如当我调用一个外部函数a,(不是函数定义时,而是函数调用的时候)

->控制器转入外部a函数的代码时, ->激活函数 a的执行上下文 ECa,将ECa放入EC堆栈,ECa变为当前激活的执行上下文, ->进入a的代码并执行a中的代码, ->执行中。。。 ->遇到内部函数 b的调用, ->激活函数 b的执行上下文 ECb,将ECb放入EC堆栈,ECb变为当前激活的执行上下文, ->执行上下文堆栈 - ECb - ECa - …… - Global EC ->进入b的代码并执行b中的代码, ->执行完b代码,退出当前执行环境 ECb,将ECb从执行上下文堆栈中推出,ECa又变为当前激活的执行上下文。 -> …….

理解了执行上下文与执行上下文堆栈的运行原理,那么理解一些其他东西,调用2次函数a其实并不是同一个EC。 e.g.5

1
2
3
4
5
6
7
function a(){
var num = 0;
return num++;
}

a();
a();

为什么两次调用a函数都是返回 1,而不是1,2。 用上下文堆栈的原理来解释是因为 在执行第一个函数a的时候,被激活的上下文为 ECa’ 当执行完毕后就被推出堆栈。再执行第二个a的调用的时候,一个新的ECa’’ 被激活放入上下文堆栈,ECa’ 和 ECa’’是相互独立的,num都会在VO阶段声明,AO阶段赋值为0。

eval,Function的执行上下文

eval会触发一个calling context的东西。会使eval影响当前的执行上下文。 e.g.6

1
2
3
4
5
6
7
8
9
10
11
eval("var x = 10");
new Function("var z = 10");

(function(){
eval("var y = 10");
console.log(y);
})();

console.log(x);
console.log(y);
console.log(z);

eval会产生自己的一个 evalContext,并且设置一个calling Context为激活它的 EC,然后菜eval中声明的变量等行为会影响到当前的EC。 虽然 z 那边直接被外部的 y 报错给打断了,其实 z 也是报错的,显然就相当于 Function 其实和一般的方法是一样的。

那么,执行上下文是什么?

那么,执行上下文是什么?那么,执行上下文是什么?那么,执行上下文是什么?那么,执行上下文是什么?那么,执行上下文是什么?。。。 只能说,它是一个抽象的对象,与JS代码执行进程有很大关系。包含 VO,SC,thisValue。 本文为个人了解观点,有不足之处望指出,谢谢~

什么是jQuery Mobile?

最初在没有接触 jQuery Mobile 之前,以为 jQuery Mobile 是独立的应用于移动端设备的 JS 库。但准确的来讲,jQuery Mobile是是一个依赖于 jQuery, 应用于手机和平板设备上的库。jQuery Mobile 不仅会给主流移动平台带来jQuery核心库,而且还包含一个完整统一的jQuery移动的UI框架。解决移动端样式兼容性问题,支持全球主流的移动平台。 下面是jQuery Mobile 兼容的移动操作系统。 以上8个主流系统应该涵盖了当前 90% 以上的移动端产品。Jquery mobile构建于Jquery ,为前端开发人员提供了一个兼容所有主流移动设备平台的统一UI接口系统。拥有出色的弹性,轻量化以及渐进增强特性与可访问性。 Jquery Mobile的策略可以很容易的概括:创建一个顶级的javascript库,在不同的智能手机和桌面电脑的web浏览器上,形成统一的用户ui. 目前Jquery Mobile的最新稳定版本为已经为正式版1.3,在2013年2月上旬发布,jQuery Mobile 1.3支持jQuery1.7.2或 1.9.1版本。

jQuery Mobile的特性

  • 构建于jQuery的核心之上。使之兼容于jq的语法,对于开发人员有最易的开发曲线
  • 兼容于所有的主流移动手机,平板电脑,电子书,和pc,iOS, Android, Blackberry, Palm WebOS, Nokia/Symbian, Windows Phone 7, MeeGo, Opera Mobile/Mini, Firefox Mobile, Kindle, Nook, 和所有的现代浏览器。
  • 轻量级 压缩后只12k,对图片的依赖程度非常低,保证了速度
  • 模块化的架构可以根据你的独特的需求用来构建最优化的应用
  • 页面和行为均基于html5标记的驱动进行配,开发效率高,对脚本的需求小
  • 渐进增强使所有的移动设备,平板电脑和pc电脑都支持核心的内容和方法。而对于新的移动平台,则可以展现像安装在设备中的应用程序一样出色的富媒体和交互的浏览体验
  • 弹性的设计可以使同样的代码在智能设备上和桌面的屏幕上都自动缩放适应。
  • 强大的ajax驱动的导航系统在保持后退按钮,收藏夹和干净的地址栏的同时完成页面转场。
  • 优秀的可访问性 一些特性比如WAI-ARIA 也包含在内,以确保页面也可以在一些屏幕阅读器(比如苹果的VoiceOver)或者其他手持设备中正常工作.
  • 支持触摸和鼠标事件增加了触摸屏设备支持的触摸,鼠标,和基于光标的输入方法的API
  • 统一的UI组件增加了触摸屏设备支持的触摸,鼠标,和基于光标的输入方法的API
  • 强大的主题样式框和主题编辑器能很容易的进行高度个性化和品牌化的的界面定制

jQuery Mobile的不足

下面也是查询网上,以及自己感觉的对于 jQuery Mobile 的不足。

  • 首先 jQuery Mobile 对于开发的简便性,兼容性 导致了需要对页面结构进行复杂化,ui组件对于元素 class 的泛滥。
  • 在性能上的一些不足,会导致在一些移动端处很卡的现象。
  • jQuery Mobile 需要对页面进行从新的构建,所以对于一些异步加载的数据就会有一些问题。

代码示例 JM 重构HTML

下面是一段使用 JM 的HTML代码,看看 JM 对其做的重构

  • Components
  • Pages & dialogs
  • Toolbars
  • Buttons
  • Content formatting
  • Form elements
  • Listviews

其中的 data-role,data-inset,data-theme等都是所谓的 HTML5 标记,JM是根据这些标记进行对页面的配置的。具体的每一个标记都是相当于 JM 中的一个控制器是。(标识数据属性) 然后结果 JM ui的从新构建后 会变成

  • Components
  • Pages & dialogs

可以看到,原先的只是一个包含 a 标签的 li 元素被重新构建成了一个非常复杂的结构。并且自动添加很多 JM ui 的样式。 当然,最后的ui效果是很不错的。 当然这部分是需要后端渲染出来,如果是前端加载,AJAX的方式请求数据渲染的话,那么模板则需要使用 JM ui 重新构建之后的 HTML 格式了,并且还需要进行一些处理。(可能还有其他较为好的方法)。 总的来说,对于一些需要快速建立简单移动站,又希望兼容性比较高的WEB开发工程师来说,jquery mobile是非常好的选着。更多学习 JM 可以参考 http://www.jqmapi.com/ .

函数定义方式

在javascript中,定义一个函数大体上有3种方式:

  • 函数申明
  • 函数表达式
  • 通过 Function 函数构造函数定义。

一般来说 方法3 不太常出现,仅仅只会在一些JS类库中实现比如解析json,解析函数的字符串格式等作用。今天主要来讲解下我所理解的前2种方式 函数声明函数表达式

函数声明

ECMA对于函数声明的定义为:

1
function Identifier ( FormalParameterListopt ) { FunctionBody }

使用函数申明的函数其名称会被提升,严格意义上讲就是在进入上下文阶段便已经处在的该上下文中的变量对象中(VO),位于代码执行之前。

1
2
3
4
alert(func);
function func(){
return "a function";
}

很显然,alert 为 func函数。 在line-1中便可以访问func,就是所谓的函数使用函数声明产生的函数提升。函数作为ECMAScript三种代码类型中的一种(还有global和eval),代码的执行都会依赖自身的一个上下文,执行上下文分为2个阶段 进入执行上下文 和 代码执行。 该处的函数声明名称被放入变量对象中,是在进入执行上下文阶段,是在代码执行阶段之前,所以可以在代码执行阶段被访问到,而无关于在代码中声明位子进行的函数声明。关于执行上下文可以参考TOM大叔的 javascript核心 和 执行上下文 ,讲的很有深度的。 对于函数声明也是有规范的:要么处于程序级别,要么处于其他函数中。除了这2处,其他位置都是违反规范的,也会导致一些浏览器的解析不正确。 比如

1
2
3
4
5
6
7
8
9
10
function foo(){
if(window===parent){
function bar(){alert(1);}
}
else{
function bar(){alert(2);}
}
bar();
}
foo();

显然讲函数声明置于if的块中是不符合ECMA规范的,一般浏览器会将 ECMA中作用域只有函数级的作用域,而没有块级作用域,所以if的块级作用域是无效的。而在与火狐下 该问题就是由于火狐浏览器下对块内函数声明与一般的浏览器有差异导致的,所以需要注意对于一般函数声明需要有规范。

函数表达式

函数表达式的声明为

1
function Identifieropt ( FormalParameterListopt ) { FunctionBody }

与函数声明的区别在于,标示符名称可以省略,并且处于表达式中,为表达式的一部分。

1
2
3
4
5
6
7
var func1 = function f(){};
var func2 = function(){};
(function func3(){});
~function func4(){};
!function func5(){};
true,function func6(){};
...

以上等等,函数部分均为自身表达式的一部分,所以都为函数表达式。 函数表达式与函数声明的一个重要的区别在于,对于 var func1 = function f(){}; 来说,规范定义其中的 f 标示符不会暴露出来,仅仅可以供 f 函数内部进行访问。 当然在此对于IE8-版本的浏览器来说又有一个重大的BUG。 对于表达式 var func1 = function f(){}; 来说,其解析就会出现较为严重的错误,它会现将表达式中的函数部分单独提取出来先进行函数声明,在进行函数表达式解析。更为严重的是,其中的 func1 != f,相当于独自创建了 函数f ,再对表达式中的函数在创建一份,并且将地址赋予 func1。解决的办法就是尽量避免IE的这些特性保证兼容所有浏览器。

实例

e.g.1

1
2
3
4
alert(a);
var a = 2;
function a(){}
alert(a)

结果为 function a(){} 和 2。 首先在进入上下文阶段时 函数声明将变量声明覆盖掉(变量的赋值是处于代码执行阶段),所以第一个 alert 弹出的为函数,当代码执行过程中,a又被重新赋值为2,故第二个 alert 显示的为2。整个流程可以与 e.g.2 进行比较就不难理解了。

e.g.2

1
2
3
4
alert(a);
var a;
function a(){}
alert(a)

其结果均为 function a(){}。 就可以看出,将a赋值为2,是在代码执行阶段进行的。那么对于

e.g.3

1
2
3
alert(a);
var a = 2;
alert(a);

可以很清楚的知道 答案为 undefined 与 2 但是对于

e.g.4

1
2
3
4
alert(a);
function a(){}
var a = 2;
alert(a)

为什么又是 function a(){} 和 2 为什么之后的变量声明中 a为undefined没有覆盖函数声明的a呢? 一般的看法为权重说法,就是函数声明一个标示符之后,如果再对该标示符进行变量声明,那么该变量声明将视为无效。而另一种看法是对于函数声明其实其流程与变量声明是一直的,在进入上下文阶段 均赋值为undefined,但是函数声明将函数引用赋给标示符这一过程,是在代码执行之前触发那一刻。 就好第一个 alert 之前那一刻,a从undefined变为了function。个人偏向于后一种看法。当然如果要知道真正的流程那么还是需要去看 ECMA3或者ECMA5详细规范吧(http://ecmascript.cn/ )。 当然说到了函数,那么势必需要知道一下关于 this 的一些资料,可以参考下上篇文章 JS的this简单理解~ 文章下面有好几篇经典的 this 介绍。

e.g.5

1
2
3
4
5
6
7
8
9
10
var f = function(){ return true; };
var g = function(){ return false; };
(function(){
alert(f());
if(g() && [] !== []){
f = function f(){ return false; }
function g(){ return true; }
}
})();
alert(f());

这是一个比较绕的题目,考察的就是上面说的2点。 先来看下答案 chrome opera IE9+等普通浏览器 会弹出 true false ,火狐这个文艺浏览器 会弹出 true true 而IE8-这2B浏览器就会弹出false true。 呵呵开玩笑。 首先说下普通浏览器 true false 吧,JS中只有函数作用域,而没有块级作用域,所以匿名自执行函数中判断的中的 g() 就为块中定义的函数g(所谓的函数提升),然后 f 被重新指向返回 false 的函数了。 所以结果为 true false。 那么第二种 true true的话,由于规范中的函数声明只可以在 程序级 或者 其它函数中,位于 if 块语句中显然会有悖于这个规范,而火狐是无法忍受该错误的,会将块中的函数声明作为 函数表达式的形式来处理。那么在 if 判断的时候就直接米有通过 那么就为 2个true了。

1
2
3
4
5
6
7
8
var g = function(){ return false; };
(function(){
alert(g());
{
function g(){ return true; }
}
alert(g());
})();

从这段代码在火狐下执行可以看出来, false 和 true 相当于 火狐下,块中的函数声明被当做了函数表达式来处理(效果上可以这么说)。 最后对于IE8-下的浏览器来说对于有名称的函数表达式,该标示符会被暴露出来。确切的来讲 IE8-下 会将 f = function f(){} 先进行函数声明一次,然后再进行一次函数声明,也导致了该处的 f 并不是会到全局变量下的 f ,而是因为已经在该函数作用域下有了f的函数声明,而覆盖该函数作用域下的 f。则有了 true false 的结果。 呵呵 能力有限,菜鸟观点,还有很多地方没有弄清楚,望大家多多指教。

什么是JS的垃圾回收机制(Garbage Collecation)

对于其他语言来说,如C,C++,需要开发者手动的来跟踪内存,而JS的垃圾回收机制使得JS开发人员无需再关系内存的情况,所有的内存分配以及回收都会由垃圾回收器自动完成,执行环境会对执行过程中占有的内存负责。其原理就是找出那些不在被使用的变量,然后释放其所占有的内存。回收器一般是按照固定的时间间隔或者预设的时间进行处理的。 e.g. 1

1
2
3
4
5
6
7
8
9
10
11
```
function test1(){
var i ={name:’nyf’};
}

function test2(){
var i ={name:’nyf’};
return i;
}
var m1 = test1();
var m2 = test2();
1
2
3
4
5
6
7
8
9
10
11

一般来说在e.g.1中,当 test1调用时,进入 test1 的环境,那么内存中会开辟存放 {name:’nyf’} 对象的内存,当调用结束后,出了 test1 的环境,那么该内存会被JS引擎中的垃圾回收器自动释放其内存。 在test2中,对象被返回,并且被变量 m2 所指向,所以虽然说在调用完 test2 后出了其环境,但是由于m2仍然持续着对对象的链接关系,所以该对象不会被释放。 但是我们需要注意上述两个例子函数中其实均有2块的内存占用,一个是变量名 i 以及对象 {name:’nyf’} ,i中只是保存着对该对象的地址值。运行test2 未被释放的只是对象,变量名i在2个方法中均被释放,i值才是JS引擎真正需要处理的目标。对于返回的对象,已经返回到上一层的环境,当没有变量再对其进行引用的时候自然也会变会被释放(个人理解)
### 垃圾回收机制的种类
目前JS的垃圾回收机制无非就是两种:1.标记清除(make-and-sweep) 2.引用计数(reference counting)

1. `标记清除:`标记清除简单的来说就是给各个变量名打上 YES or NO的标签以供JS引擎进行处理(当然打什么标签自己理解即可)。在和执行上下文类似的的环境中当变量名称进入环境的时候,那么变量会被打上 YES。一般来说是绝对不会释放被打上 YES 标签的变量内存的,一旦变量在出了该环境时,变会被打上 NO 标签(和作用域貌似有点像),JS引擎会在一定时间间隔或者设置的时间来进行扫描,对NO标签的进行剔除以释放其内存。

2. > `引用计数:`(查了很多资料,还是无法找到其真正的计算方式) 一般来说,引用计数的含义是跟踪记录每个值被引用的次数。当声明一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数便是1,如果同一个值又被赋给另一个变量,则该值的引用次数加1,相反,如果包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1。当这个值的引用次数为0时,说明没有办法访问到它了,因而可以将其占用的内存空间回收。(感觉是有问题的)

> 对于引用计数,我们需要知道如果具有循环引用,那么其计数问题就会暴露,导致计数永远不为0而无法释放内存,导致内存泄露,具体事例如下: `e.g.2`

1
2
3
4
5
6
function(){
var a = {};
var b = {};
b.pro = a;
a.pro=b;
}
1
2
3
4
5

如果e.g.2使用引用计数的话就会导致问题,内存无法被释放,导致内存无故消耗占用。
### IE的垃圾回收机制问题
除了一些极老版本的IE,目前市面上的JS引擎基本采用标记清除来除了垃圾回收。但是需要注意的是IE中的DOM由于机制问题,是采用了引用计数的方式,所以会有循环引用的问题,如: `e.g.3`

1
2
3
4
var ele = document.getElementById(“element”);
var obj = new Object();
ele.obj = obj;
obj.ele = ele;

```

这边就会倒是问题,内存无法再执行完毕后释放 解决方法其实也很简单,当所有的代码完毕末尾处只需要对变量进行 null 赋值即可。 有很多不足与错误,希望各位指出谢谢

闭包的概念

首先也来简单的说说,JS的闭包到底是什么?较为官方的解释为:

Closure : A “closure” is an expression (typically a function) that can have free variables together with an environment that binds those variables (that “closes” the expression). 所谓“闭包”,指的是一个拥有多个自由变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

还有一个对于闭包的描述我认为也挺不错的:

A closure is a combination of a code block (in ECMAScript this is a function) and statically/lexically saved all parent scopes.Thus, via these saved scopes a function may easily refer free variables. 闭包是一个组合的代码块(在ECMAScript中是函数),并且静态保存所有父级的作用域。通过这些保存的作用域来搜寻到函数中的自由变量。

按照以上两条描述其实可以看出,闭包不是一个名词,而更偏向于一个形容词来形容一种结构,闭包不仅仅是简单地是在外部函数返回一个内部函数那么简单,事实上从广义的理论上来讲ECMAScript中所有的函数均可以理解为闭包。说了那么多模糊的,那么来具体理解下,到底,什么才是闭包。

讲在闭包前的知识

首先要了解一些基本的与闭包有关的知识。(以下并不完全正确~) 执行上下文(execution context):执行上下文是ECMAScript中用来描述JS代码执行的一个抽象概念。所有的JS代码都是在某个执行上下文的环境中运行的。在当前执行上下文中调用另一个function的时候就会进入一个新的执行上下文环境,直到当前function执行完毕的时候就会离开此新的执行上下文环境,重新返回之前的执行上下文环境。来回的切换上下文执行环境也就构成了一个执行上下文堆栈。 作用域链(scope chain):每一个执行上下文环境都会和一个作用域链相互关联。作用域链可在进入某一个执行上下文环境的时候来进行对标识符(identifier)求值。很多时候作用域链中不仅仅只有一个对象,可以想象作用域链也为一个堆栈,查找标识符会从链首开始,然后依次往后从对象中查找标识符,在对每个对象进行查找时也对其原型链进行查找。 当切换一个执行上下文环境的时候会进行以下的操作:

  1. 当函数创建时(注意不是调用时)的时候已经静态的确定了[[scope]]属性(稍后会详细讲一下)。
  2. 该处才是切换的时候进行的第一步操作,创建活动对象(activation object),活动对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化,在函数执行上下文的时候变量对象(variable object)是无法直接访问的,所以由AO扮演VO角色,包括arguments属性等。
  3. 创建作用域链,作用域链 = 活动对象(AO) + [[scope]],[[scope]]为function的一个内部属性,与function的创建方式与所在代码位置相关。
  4. 变量的初始化。这个就简单的理解了,在函数运行时对一些变量进行赋值等一些操作。

我们所了解的闭包

E.g 1:

1
2
3
4
5
6
7
8
9
10
11
12
function foo(){
var x = 0;
return function(){
alert(x++);
}
}
var retFunc = foo();
//foo内部返回一个function 可以随意访问foo的内部变量 x
retFunc (); // 0
retFunc (); // 1
var x = 0;
retFunc (); // 2

这是我们最常见的使用闭包特性来进行的处理。此处的 x 是从返回的内部函数的[[scope]]上搜索而来的,静态作用域。 再来一个例子可能可以更鲜明一点的理解静态的含义 E.g 2:

1
2
3
4
5
6
7
8
var x = 0;
function foo(){
alert(x);
}
(function(){
var x = 1;
foo();
})();// 弹出 0

静态就是在此处并不弹出0的原因,因为每个函数的[[scope]]均是在函数定义的时候就已经确定,并且不再修改了的,知道与函数一起销毁。 foo在定义的时候[[scope]]中就为全局变量的 x ,而在哪里调用是不会再影响foo的[[scope]]的。 我们最常遇到的闭包带来的问题就是循环中的闭包影响(更多的时候是在操作DOM节点的时候的循环绑定事件,并且还是用的循环中的循环数值作为参数): E.g 3:

1
2
3
4
5
6
var data =  [];
for(var i =0;i<3;i++){
data[i] = function(){
alert(i);
}
}

当我们在调用data1,data2,data3时均会返回 3。因为data数组中的每个函数的[[scope]]都存放着全局的 i,当在调用的时候 i 已经在循环中被赋值为3,故而不管调用哪一个方法,均会从该方法的[[scope]]中查找 i,便是目前的全局i,值为3。 解决办法便是在多制造出一层函数。(在[[scope]]中后进入的优先级更高,从栈首开始搜索。) E.g 4:

1
2
3
4
5
6
7
8
9
var data =  [];
for(var i =0;i<3;i++){
data[i] = (function(x){
return function(){
alert(x);// 此处便有所不同
alert(i); // 此处仍是3
}
})(i);
}

使得每次alert的值并不是从[[scope]]中查询全局的i值。

闭包的原理

闭包是代码块和创建该代码块的上下文中数据的结合。技术上来说创建函数的父级上下文中的数据是完全保持在该函数的内部属性[[scope]]中的,这也是的我们的内部函数能够访问外部函数的自由变量。所以理论上来说,所有函数都可以当做闭包。 闭包实质上是通过函数内部的[[scope]]属性来实现的。[[scope]]是所有父变量对象的层级链,处于当前函数上下文之上,在函数创建时存于其中。 [[scope]]在函数创建时被存储--静态(不变的)直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。

我所认为的闭包

闭包不简单,也不复杂。在JS中纯粹只是JS实现机制的一种产物,为我们所利用。理解作用域,作用域链,[[scope]]等JS知识,闭包自然而然的就会有了一定了解。本文如有错误,请各位矫正,谢谢~ 才疏学浅,无法将闭包讲的更深入,具体可以参考一下文章: http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html http://www.cnblogs.com/TomXu/archive/2012/01/31/2330252.html http://baike.baidu.com/view/648413.htm

Lazyload

Lazyload 是通过延时加载JS和CSS文件来实现按需加载,以保证在页面刚刚开始呈现的时候提高速度,提高用户体验。 并且LazyLoad是非常小的,压缩完后(gzipped后只有 996 bite),很容易实现装载外部JavaScript和CSS文件的需求。并且当你指定数组的 url 时,LazyLoad将自动加载资源,同时保证并行执行顺序加载。 其实现的对JS动态加载在不同浏览器的处理,以及CSS加载在不同浏览器的处理。实现了支持 Firefox 2+, IE6+, Safari 3+ (包括移动Safari), Google Chrome, and Opera 9+.

Lazyload 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Load a single JavaScript file and execute a callback when it finishes.
LazyLoad.js("http://example.com/foo.js", function () {
alert("foo.js has been loaded");
});

// Load multiple JS files and execute a callback when they've all finished.
LazyLoad.js(["foo.js", "bar.js", "baz.js"], function () {
alert("all files have been loaded");
});

// Load a CSS file and pass an argument to the callback function.
LazyLoad.css("foo.css", function (arg) {
alert(arg);
}, "foo.css has been loaded");

// Load a CSS file and execute the callback in a different scope.
LazyLoad.css("foo.css", function () {
alert(this.foo); // displays "bar"
}, null, {foo: "bar"});

Lazyload 源码分析

LazyLoad 更容易的无阻塞延时加载一个或多个JavaScript 或 CSS 文件 在需求期间或之后呈现的web页面。 支撑 Firefox 2+, IE6+, Safari 3+ (包括移动Safari), Google Chrome, and Opera 9+. 其他浏览器为官方不标注支持。 关注 https://github.com/rgrove/lazyload/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
Copyright (c) 2011 Ryan Grove <ryan@wonko.com>
*/

LazyLoad = (function (doc) {
// -- 私有变量 ------------------------------------------------------
// 用户代理和功能测试信息
var env,
// 指向元素 用来延迟填充.
head,
// 任何目前在进行的请求
pending = {},
// 使用次数来判断样式表是否已经加载 数值太高可能停滞。
pollCount = 0,
// 请求队列
queue = {css: [], js: []},
// 指向 文档的样式表列表
styleSheets = doc.styleSheets;

// -- 私有方法 --------------------------------------------------------
/**
创建并返回一个制定名和属性的 HTML 元素
@method createNode
@param {String} name element name
@param {Object} attrs name/value mapping of element attributes
@return {HTMLElement}
@private
*/
function createNode(name, attrs) {
var node = doc.createElement(name), attr;
for (attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
node.setAttribute(attr, attrs[attr]);
}
}
return node;
}

/**
当当前指定类型的资源完成加载时调用。执行回调并加载下一个队列中的资源(如果有)
@method finish
@param {String} type resource type ('css' or 'js')
@private
*/
function finish(type) {
var p = pending[type],
callback,
urls;
// 判断是否有当前类型的资源加载
if (p) {
callback = p.callback;
urls = p.urls;

urls.shift();
pollCount = 0;

// 如果为最后一个待加载的URL 执行回调并且开始下一个队列中的请求(如果有)
if (!urls.length) {
callback &amp;&amp; callback.call(p.context, p.obj);// 若有回调 以panding[type]中属性来执行
pending[type] = null;
queue[type].length &amp;&amp; load(type);// 队列中如还有当前类型的资源 加载该类型资源
}
}
}

/**
当前代码 the <code>env</code> 环境变量和用户代理和功能测试信息。
@method getEnv
@private
*/
function getEnv() {
var ua = navigator.userAgent;
env = {
// 如果这个浏览器支持禁用异步模式动态创建脚本节点
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
async: doc.createElement('script').async === true
};

(env.webkit = /AppleWebKit\//.test(ua))
|| (env.ie = /MSIE/.test(ua))
|| (env.opera = /Opera/.test(ua))
|| (env.gecko = /Gecko\//.test(ua))
|| (env.unknown = true);
}

/**
加载指定的资源,或者在队列中如果没有指定的资源加载下一个指定类型资源。
如果一个指定类型资源已经被加载,新的请求将被排队,直到第一个请求完成。
当数组指定的资源urls,这些url将被载入并行如果它可行,同时保留执行顺序。所有
浏览器支持并行加载CSS,但只有Firefox和Opera支持并行加载的脚本。在其他浏览器,脚本将
排队并且在一个时间片段加载一次,以确保正确的执行顺序。
@method load
@param {String} type 资源类型 ('css' or 'js')
@param {String|Array} urls (可选) 需加载的URL或者URLs数组
@param {Function} callback (可选) 当资源加载完毕后
@param {Object} obj (可选) 传递给回调函数的对象参数
@param {Object} context (可选) 如果提供, 回调函数将被执行在这个对象的上下文参数中
@private
*/
function load(type, urls, callback, obj, context) {
var _finish = function () { finish(type); },
isCSS = type === 'css',
nodes = [],
i, len, node, p, pendingUrls, url;
env || getEnv();

if (urls) { // 有urls参数
// 当 urls 为 string 类型,处理为单元素数组
// urls.concat()为复制数组(深度复制)使处理其不影响方法调用时的参数(对象在参数传递时是传址传递)
urls = typeof urls === 'string' ? [urls] : urls.concat();

// 为每个URL创建一个请求对象,如果指定为多个url,则当所有url加载完毕后执行回调
//
// 可惜,Firefox和Opera浏览器是唯一能够并行加载并且同时保留执行顺序的浏览器。
// 在其他浏览器 脚本必须被逐一加载来保证顺序
//
// 所有浏览器对于 CSS 样式表的话 并行加载无先后顺序影响 都是简单的下载
if (isCSS || env.async || env.gecko || env.opera) {
// 并行加载 当为CSS样式表 异步加载 Firefox和Opera 时 直接扔进队列中并行加载。
queue[type].push({
urls : urls,
callback: callback,
obj : obj,
context : context
});
} else {
// 否则 逐一加载保证顺序。
for (i = 0, len = urls.length; i &lt; len; ++i) {
queue[type].push({
urls : [urls[i]],
callback: i === len - 1 ? callback : null, // 只在最后一个时放入回调函数
obj : obj,
context : context
});
}
}
}
// 处理完urls参数进行队列处理

// 如果之前加载的要求这种类型目前还在进行中,将等待。否则,抓住队列中的下一项。
if (pending[type] || !(p = pending[type] = queue[type].shift())) {
return;
}

head || (head = doc.head || doc.getElementsByTagName('head')[0]);
pendingUrls = p.urls;

// 循环建立请求
for (i = 0, len = pendingUrls.length; i &lt; len; ++i) {
url = pendingUrls[i];

// 火狐下不支持link节点的onload事件 以创建 style 标签 @import 引用来实现调用回调
if (isCSS) {
node = env.gecko ? createNode('style') : createNode('link', {
href: url,
rel : 'stylesheet'
});
} else {
node = createNode('script', {src: url});
node.async = false;// 设为同步加载 保证顺序
}

node.className = 'lazyload';
node.setAttribute('charset', 'utf-8');

if (env.ie &amp;&amp; !isCSS) {// IE的 script 加载完毕触发回调条件
node.onreadystatechange = function () {
if (/loaded|complete/.test(node.readyState)) {
node.onreadystatechange = null;
_finish();
}
};
} else if (isCSS &amp;&amp; (env.gecko || env.webkit)) {
// Gecko和WebKit不支持link节点的onload事件。
if (env.webkit) {
// 在WebKit,我们可以轮询修改文档。样式表找出当样式表已经加载。
p.urls[i] = node.href; // 解决相对url(或轮询不会工作)
pollWebKit();
} else {
// 在Gecko,我们可以导入请求的URL到
<style>节点和轮询node.sheet.cssRules的存在。
node.innerHTML = '@import "' + url + '";';
pollGecko(node);
}
} else {
node.onload = node.onerror = _finish;
}

nodes.push(node);
}

for (i = 0, len = nodes.length; i < len; ++i) {
head.appendChild(nodes[i]);
}
}

/**
当样式表在Gecko中加载的时候 开始轮询来判断是否完成加载。在所有样式表完成加载 或者10S之后停止轮询防止无限循环
在此使用基于@import的跨域技术,和一个同域的实现
http://www.zachleat.com/web/2010/07/29/load-css-dynamically/
@method pollGecko
@param {HTMLElement} node Style node to poll.
@private
*/
function pollGecko(node) {
var hasRules;

try {
// 无需存储或者在此引用该值 但是如果不引用 编辑器就认为是无用的并且移除它。
hasRules = !!node.sheet.cssRules;
} catch (ex) {
// 一个例外意味着样式表仍然是在加载。
pollCount += 1;

if (pollCount < 200) { // 循环次数限制 共10s
setTimeout(function () { pollGecko(node); }, 50);
} else {
// 轮询10秒后,还是没有结果发生。那么停止轮询和完成未决请求进一步判断 避免阻塞请求。
hasRules && finish('css');
}

return;
}

// 如果执行到此,则样式表加载。
finish('css');
}

/**
在WebKit中 开始轮询来判断当等待样式表已经完成加载,在所有样式表完成加载 或者10S之后停止轮询防止无限循环
@method pollWebKit
@private
*/
function pollWebKit() {
var css = pending.css, i;
if (css) {
i = styleSheets.length;
// 匹配styleSheet中的href 来判断是否加载完毕
while (--i >= 0) {
if (styleSheets[i].href === css.urls[0]) {
finish('css');
break;
}
}

pollCount += 1;

if (css) {
if (pollCount < 200) {
setTimeout(pollWebKit, 50);
} else {
// 轮询10秒,但是什么也没有发生,这可能表明,样式表中已删除文件之前就有机会负载。停止轮询和完成等待请求以防止阻塞进一步的请求。
finish('css');
}
}
}
}

return {

/**
模块模式来暴露接口

请求指定CSS URL或URLs,当他们完成加载执行指定的回调函数(如果有的话)。
如果指定的是一个一个数组的urls,样式表将被并行加载在和所有的样式表加载完毕后执行回调。

@method css
@param {String|Array} urls 需要加载的单个或者数组形式的url
@param {Function} callback (可选)加载完毕所需要执行的回调函数
@param {Object} obj (可选) 回调函数需要传递的参数
@param {Object} context (可选) 提供回调函数的执行上下文
@static
*/
css: function (urls, callback, obj, context) {
load('css', urls, callback, obj, context);
},

/**
请求指定的JavaScriptURL或URLs并当他们完成加载执行指定的回调函数(如果有的话)。
如果是指定一个url的数组和当浏览器支持,脚本将被并行载入,当完成所有的脚本加载后执行回调。

目前,只有Firefox和Opera支持并行加载脚本并且保存执行顺序。
在其他浏览器,脚本将排队和逐一加载一次,以确保正确的执行顺序。

@method js
@param {String|Array} urls 需要加载的单个或者数组形式的url
@param {Function} callback (可选) callback 加载完毕所需要执行的回调函数
@param {Object} obj (可选) 回调函数需要传递的参数
@param {Object} context (可选) 提供回调函数的执行上下文
@static
*/
js: function (urls, callback, obj, context) {
load('js', urls, callback, obj, context);
}

};
})(this.document);

最近找了一些关于setTimeout与setInterval的一些资料,发现对于偏好各个都有。 但是我认为setTimeout与setInterval既然存在,总有他们自己的意义,过分的去深究到底孰优孰略是完全没有什么必要的,虽然本人更偏向setTimeout一些,但我绝对不会排斥setInterval。 我一般喜欢使用一段简单的代码来用 setTimeout 模拟 setInterval

1
2
3
4
5
function foo(){
//... 执行代码
setTimeout(foo,2000);
}
foo();

上述代码可以很好的模拟 setInterval 。 首先看一段实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function test(){
var flog = +new Date();
while(true){
if(+new Date() - flog > 3000){
console.log("after");
console.log((+new Date()/1000).toFixed(0) + "s");
break;
}
}
}
function foo(){
test();
setTimeout(foo,2000);
}
foo();

test()中我使用while来占有内存,达到控制进程的效果(大量消耗内存,仅供测试)。下面为控制台的一段输出,间隔为5s。

1
2
3
4
5
6
after 
1354024132s
after
1354024137s
after
1354024142s

感觉是理所当然的,而 setInterval 就不一样了,

1
2
3
4
5
6
7
8
9
10
11
function test(){
var flog = +new Date();
while(true){
if(+new Date() - flog > 3000){
console.log("after");
console.log((+new Date()/1000).toFixed(0) + "s");
break;
}
}
}
setInterval(test,2000);

其结果片段就不一样了,

1
2
3
4
5
6
after 
1354024417s
after
1354024420s
after
1354024423s

其间隔为3s。 上述为什么会有不一致性呢? 这个可能需要从2个的根本区别说起, setTimeout() : 用于在指定的毫秒数后调用函数或计算表达式; setInterval() : 按照指定的周期(以毫秒计)来调用函数或计算表达式. 会不停地调用函数,直 到 clearInterval() 借用大牛的几句话:

  1. JavaScript引擎是单线程的,强制所有的异步事件排队等待执行
  2. setTimeout 和 setInterval 在执行异步代码的时候有着根本的不同
  3. 如果一个计时器被阻塞而不能立即执行,它将延迟执行直到下一次可能执行的时间点才被执行(比期望的时间间隔要长些)
  4. 如果setInterval回调函数的执行时间将足够长(比指定的时间间隔长),它们将连续执行并且彼此之间没有时间间隔。

举个例子: 我有一个需要执行1s的方法,我如果是 setTimeout 延时2s执行,那么从0开始 在 2 5 8···的时间点开始执行,而setInterval却在 2 4 6···时刻开始执行。 可以想象,当延时时间比执行时间短的时候 setInterval 会出现什么情况,当不断叠加不断消耗内存的风险。 总而言之,setTimeout 唯一存在的问题就是对于执行的时间点误差较大,但对于性能方面会比较友好,当然如果所需执行的为每一时间段,并且运行量较小,那就考虑选择 setIntervsl。