编写一个H5开发JS库 -- JUGG.js ( PART 3 )

PART 2 中实现的时 attr 操作,即 HTML 元素的属性操作方法。

PART 3 将要实现为 DOM 绑定事件。目前仅考虑处理简单的 DOM 事件。

扩展一个 $.fn.on 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.fn = {
// ...
on : function( type, fn ){
_utils.forEach.call( this, function( el ){
el._evts || ( el._evts = {} );
el._evts[ type ] || ( el._evts[ type ] = [] );
el._evts[ type ].push( { callback : fn } );
el.addEventListener( type, fn );
} );
},
// ...
};

通过上面的实现可以看出,JUGG 的 on 方法为调用它的元素绑定一个 _evts 对象,该对象用于维护元素上绑定的事件和处理函数。JUGG 没有在模块内部单独设置一个统一的事件的对象,考虑到基于元素绑定事件有更多的便利,比如在删除元素时,连同绑定的事件自然被销毁、代码可读性强、方便调试等。

有了 on,就需要一个反方向的 off:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$.fn = {
// ...
off : function( type, fn ){
_utils.forEach.call( this, function( el ){
if ( ( ! el._evts ) || ( ! el._evts[ type ] ) ) return false;
var
handlers = el._evts[ type ],
len = handlers.length,
i = 0;
for ( ; i < len; i++ ){
if( fn === undefined || handlers[ i ].callback === fn ){
el.removeEventListener( type, handlers.splice( i, 1 )[ 0 ].callback );
if( fn ) break;
}
}
} );
},
// ...
};

off 方法也非常简单,对于有第二个参数的调用,认为是移除特定的方法,而对于没有第二个参数的调用,认为是移除该事件的全部方法。

最后需要一个 trigger 方法用于触发事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$.fn = {
// ...
trigger : function( type ){
_utils.forEach.call( this, function( el ){
if( ( ! el._evts ) || ( ! el._evts[ type ] ) ) return false;
var
handlers = el._evts[ type ],
len = handlers.length,
i = 0,
evt;
evt = document.createEvent( 'Event' );
evt.initEvent( type, true, true );
el.dispatchEvent( evt );
} );
},
// ...
};

目前为止,事件的处理都是非常简单的处理,没有考虑到自定义事件和移动端各种手势事件的封装。那将是一大块内容,会在后面补充。这里先写一个大致的框架内容。

下一个 Part 将尝试实现 DOM 节点的相关操作。
Part 3 end, To be continue…

转载请注明出处:http://yyblog.mocoer.com/2014/12/16/编写一个H5开发JS库 -- JUGG.js ( PART 3 )/

编写一个H5开发JS库 -- JUGG.js ( PART 2 )

PART 1 中主要说明了 $ 函数的实现思路,并实现了 class 的操作。

PART 2 将要实现 DOM 的属性操作,即 attr 方法。

扩展一个 $.fn.attr 方法如下:

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
$.fn = {
// ...
attr : function( name, value ){
var key;
// 通过检测 arguments 对象的 length 值
// 做不同的处理( get, set, remove )
switch ( arguments.length ){
case 1 :
// 只有一个参数且为对象类型,则认为是设置元素属性或删除元素属性
if ( typeof name === 'object' ){
for( key in name ){
_utils.forEach.call( this,
function( el ){
name[ key ] === '' ?
el.removeAttribute( key ) :
el.setAttribute( key, name[ key ] );
} );
}
}
// 只有一个参数且为字符串类型,则认为是获取元素属性
else if( typeof name === 'string' ){
return this[ 0 ].getAttribute( name );
}
break;
case 2 :
// 如果有两个参数,则只可能是设置单个元素属性值或删除单个元素属性值
// 第一个参数必须为字符串类型
if ( typeof name != 'string' ) return false;
_utils.forEach.call( this,
function( el ){
value === '' ?
el.removeAttribute( name ) :
el.setAttribute( name, value );
} );
break;
}
// 为实现链式调用,方法最后均需要返回 this
return this;
},
// ...
};

现在可以这样使用 $:
html

1
<a>test</a>

javascript

1
2
3
4
5
$('a').attr('data-test'); // => null
$('a').attr('data-test', 1); // 添加属性 data-test 并设置其值为 1
$('a').attr('data-test', ''); // 删除属性 data-test
$('a').attr({ 'data-test' : 1, dataTest : 2 }); // 设置多个属性
$('a').attr({ 'data-test' : '', dataTest : '' }); // 删除多个属性

后面,我们会尝试添加 on/off/trigger 方法,即事件的简单处理。
Part 2 end, To be continue…

转载请注明出处:http://yyblog.mocoer.com/2014/12/15/编写一个H5开发JS库 -- JUGG.js ( PART 2 )/

编写一个H5开发JS库 -- JUGG.js ( PART 1 )

在 H5 开发方面,目前流行的 js 库已经有很多,例如:类似 jquery.js 的 zepto.js;专注 guesture 事件的 hammer.js/quo.js 等。
它们简化了 H5 应用或网页的前端开发工作。当然,学习这些库的使用是有必要的,而我更希望能通过“重复造轮子”来深入理解它们,同时编写更适合自己工作需要的 js 库!
库中可能会用到一些之前写过的工具函数,同时借鉴了 zepto.js 和 hammer.js 中的一些的实践。
JUGG.js 的重点将放在用户与移动设备交互事件的处理。

要点

  1. 起名字
  2. 规划几个对象和变量
  3. $
  4. 功能开发:class 操作

1. 起名字

JUGG 是陪伴了自己多年的竞技游戏 Dota 中的英雄角色,简介:他出剑似舞蹈般灵动轻盈,无论多坚固的护甲都已悄然破碎:他行动如诗歌般优雅从容,无论多强的敌人弹指间灰飞烟灭。是的,他追求完美,追求人剑合一的至高境界。成为那无人能敌的剑圣(js)便是他的宿命。
因此,我希望 JUGG.js 拥有敏捷、轻量、强大的特点,专注服务于移动端,毕竟移动端H5的开发考虑最重要的两个问题:代码量、代码执行效率,要求我们使用轻量敏捷的开发库。

2. 规划几个对象和变量

首先是几个只在模块内部维护的变量:

1
2
3
4
5
var
J, // JUGG 类
_jugg = {}, // 承载了与 JUGG 类相关的内部方法
_utils = {}, // 承载了所有工具方法
_jid = 0, // 内部维护的唯一 J 实例 id

然后是一个对外接口:

1
var $; // 对外接口,这里直接仿照了 jquery

3. $

现在进入真正的开发工作,首先我们需要将 $ 包装成一个模块。关于模块的介绍

1
2
3
4
( function( root ){
// ...
root.$ = $; // 这样就完成了对外暴露接口
} )( window ); // 这里说明改模块需要运行在浏览器环境下

现在需要做进一步考虑,首先需要明白的是:$ 是一个函数,该函数返回一个对象这里称作 o,我们希望 o 拥有各种各样的方法,比如通过这样调用 o.hasClass( ‘btn’ ) 来知道 o 是否包含 btn 这个 class 名。
一个思路就是:用 $ 函数生成J类的实例,该实例继承J类的所有原型方法。关于原型继承的介绍
根据这个思路来扩充之前的代码,先不急考虑传入 $ 函数的参数如何处理:

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
( function( root ){
var
J, // JUGG 类
_jugg = {}, // 承载了与 JUGG 类相关的内部方法
_utils = {}, // 承载了所有工具方法
_jid = 0, // 内部维护的唯一 J 实例 id
$; // 对外接口
// 先定义一些常用的工具函数,不是重点
_utils = {
isArray : Array.isArray || function(object){ return object instanceof Array },
slice : Array.prototype.slice,
forEach : Array.prototype.forEach
};
// 定义 JUGG 类
// 生成的对象是一个类数组
J = function(){
this.jid = _jid++; // jid 属性
};
// $ 是一个函数(先不考虑传入参数)
// 它返回一个 J 实例
$ = function(){
return new J();
};
root.$ = $; // 这样就完成了对外暴露接口
} )( window ); // 这里说明改模块需要运行在浏览器环境下

现在验证一下 $ 函数:

1
2
3
var a = $();
console.log( a ); // => J {jid: 0, constructor: function, test: function}
a.test(); // => "test"

代码如期运行。现在,需要让 $ 函数能够接收并处理参数。回想 jquery 的 $,可以接收的参数有:html字符串、 jquery 对象、dom 对象、id选择器、class选择器等等。也就是说,$ 函数应该能够识别并处理多种类型的传入参数。需要注意的时,虽然参数种类不同,但是 $ 的返回总是一个 J 类型的对象。

为了实现多种类参数的识别和处理,我们的 $ 函数不能直接这样写: return new J( / 参数 / );,因为 J 类目前的没有处理参数的能力。所以需要定义一个 init 方法来负责处理参数,然后又 init 函数最后调用 return new J( /参数 / )。我们可以这样写(扩充一个 _jugg 的方法:init, J(这里又转弯定义了一个 J 来 return new J() 的原因会在后面的 $.fn 定义中解释), jsJ, query ):

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
/* ... */
// J 生成的实例我们定义成了一个类数组(以数组为索引并有length属性的对象)
J = function( dom ){
var i = 0,
len = dom ? dom.length : 0;
for( ; i < len; i++ ){
this[ i ] = dom[ i ];
}
this.jid = _jid++;
this.length = len;
};
_jugg = {
J : function( dom ){
return new J( dom );
},
isJ : function( object ){
// 这个返回true的条件是 object (直接或间接)原型继承自 _jugg.J.prototype
return object instanceof _jugg.J;
},
// 这里是针对不同类型的传入参数做不同的处理
// 比如,如果传入的是字符串:'div', 那么应该用 query 处理,得到元素
// 如果传入的是一个数组,那么应该认为 dom(元素组成的数组) 就是 selector
// 然后对 selector 直接执行 init,生成 J 对象
// 如果传入的是一个 J 对象,那么直接返回这个对象,不再做 init 处理
init : function( selector ){
var dom;
if ( _jugg.isJ( selector ) ) {
return selector;
}else if( _utils.isArray( selector ) ){
dom = selector;
}else{
dom = _jugg.query( selector );
}
// 这里转弯调用 _jugg.J 而不是直接 return
return _jugg.J( dom );
}
};
$ = function( selector ){
return _jugg.init( selector );
};
/* ... */

现在可以给 $ 函数传入参数了,比如:
html

1
<a>test</a>

javascript

1
2
var a = $( 'a' );
console.log( a ); // => J {0: a, jid: 2, length: 1, constructor: function...}

从上面的返回可以看到,$ 返回了一个类数组,有一个元素 a(dom)。代码按照现在的思路运行的没什么问题。现在我们就需要尝试为 $ 函数的返回(上面这个例子中就是 J {0: a, jid: 2, length: 1, constructor: function…} )添加一些方法,比如我们需要一个 hasClass 的功能,来判断 a 元素是否包含某个指定的 class 名:

1
$('a').hasClass();

要实现上面的功能,我们要做的是定义 J 类的prototype属性。详细的解释参见这里;

4. 功能开发:class 操作

在我开发的项目中,HTML 元素的 class 操作一定是最频繁的 DOM 操作了。现在尝试增加 class 操作的方法。
通过 ECMASCRIPT 5 的 classList API 可以非常方便的完成这个工作!而思路就是:重新定义 J 类的prototype,添加操作 class 的方法:

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
$.fn = {
// 这里之所以没有使用 J 是因为
// 通过 $() 生成的 J 实例是通过 _jugg.init 间接完成的(这个过程做了一些必要的处理,同时起到保护 J 类的作用)
// constructor 不直接指向 J
// 但其实这一点没那么重要,只是为了兼容其他程序和完整起见
constructor : _jugg.J,
hasClass : function( name ){
// 通常情况下,我们想判断的时指定的**一个**元素的 class 属性是否包含某个 class 名
// 因此我们要做的是取出第一个元素(这里 this 是 J 的一个实例,是一个类数组)
// 然后仅对这个元素进行判断
// classList 是 ECMASCRIPT 5 新增 API,使得操作 class 变得非常方便
return ( name ) && ( typeof name === 'string' ) &&
this[ 0 ] && ( this[ 0 ].classList.contains( name ) );
},
addClass : function( name ){
if( ! ( name && typeof name === 'string' ) ) return false;
_utils.forEach.call( this, function( el ){
el.classList.add( name );
} );
return this;
},
removeClass : function( name ){
if( ! ( name && typeof name === 'string' ) ) return false;
_utils.forEach.call( this, function( el ){
el.classList.remove( name );
} );
},
toggleClass : function( name ){
if( ! ( name && typeof name === 'string' ) ) return false;
_utils.forEach.call( this, function( el ){
el.classList.toggle( name );
} );
return this;
}
// ...
};
// 这句话的关键性在于,它让所有 $ 函数返回的对象有了 $.fn 这个对象的所有方法
_jugg.J.prototype = J.prototype = $.fn;

现在可以这样使用 $:
html

1
<a>test</a>

javascript

1
2
3
4
$( 'a' ).hasClass( 'test' ); // => false
$( 'a' ).addClass( 'test' );
$( 'a' ).removeClass( 'test' );
$( 'a' ).toggleClass( 'test' );

接下来的工作就是扩充更多的方法,优化它们的实现…
Part 1 end, To be continue…

转载请注明出处:http://yyblog.mocoer.com/2014/12/12/编写一个H5开发JS库 -- JUGG.js ( PART 1 )/

写自己的JavaScript工具-addClass/removeClass/hasClass

要点

  1. ECMAScript 5 新增元素属性:classList
  2. 为ECMAScript 3 伪造一个 classList

1. ECMAScript 5 新增元素属性:classList

在 ECMAScript 3 中,我们可能是这样操作 HTML 的 class 属性的,下面是一些简单实现,后续会进行改进:

html

1
<a id="start" class="working disabled">Start</a>

javascript

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
var btn = document.getElementById( 'start' );
// 获取 class 列表
btn.className; // => "working disabled"
// 删除指定 class 名 'working'
var removeClass = function( el, c ){
el.className = el.className.replace(new RegExp( '\\s*\\b' + c + '\\b', 'g' ), '' );
}
removeClass( btn, 'working' );
// 判断是否包含指定 class 名 'working'
var hasClass = function( el, c ){
return el.className.search('\\b' + c + '\\b') !== -1;
}
hasClass( btn, 'working' );
// 添加一个 class 名 'textBtn'
var addClass = function( el, c ){
if( el.className === '' ){
el.className = c;
}
else if( ! hasClass( el, c ) ){
el.className += ' ' + c;
}
}
addClass( btn, 'textBtn' );
// 对于一个给定的 class 名 'disabled', 如果存在,则删除,如果不存在,则添加
var toggleClass = function( el, c ){
hasClass( el, c ) ? removeClass( el, c ) : addClass( el, c );
}
toggleClass( btn, 'disabled' );

声明各种函数的做法(像上面这样)的弊端是显而易见的:破坏全部环境;代码零散。

除此之外,对于频繁的操作 class 操作,我们希望能有相应的 API 帮助我们快速操作 class 属性。ECMAScprit 5 便提供了这个 API:classList。
还是上面的例子,在 ECMAScript 5 中我们可以这样去做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var btn = document.getElementById( 'start' );
// 获取 class 列表,这次将直接获取一个 class 名的**类数组对象**
// 但它不是真正的数组,如果尝试使用操作数组的方式操作它,将会报错
// 因为它没有数组常有的方法,取而代之的是一些专用方法,但依然有数组的 length 属性和 toString() 方法
btn.classList; // => [ 'working', 'disabled' ]
// 使用 push 会报错
btn.classList.push('textBtn'); // => TypeError: undefined is not a function
// 删除指定 class 名 'working'
btn.classList.remove( 'working' );
// 判断是否包含指定 class 名 'working'
btn.classList.contains( 'working' );
// 添加一个 class 名 'textBtn'
btn.classList.add( 'textBtn' );
// 对于一个给定的 class 名 'disabled', 如果存在,则删除,如果不存在,则添加
btn.classList.toggle( 'disabled' );

可见 classList 为操作 HTML 元素的 class 属性提供了方便。下面我们改进上面的 ECMAScript 3 版的方法,并与 classList 一同封装成一个类:ClassList。

2. 为ECMAScript 3 伪造一个 classList

这里采用模块的一般写法:

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
( function namespace(){
var root = this;
// 输出 classList 全局对象(函数)
root.classList = function( el ){
return el.classList || new ClassList( el );
}
// 判断的 class 名不能是空字符串或者包含空格符的字符串
var checkClass = function( c ){
if( c.length === 0 || c.indexOf( ' ' ) !== -1 ){
throw new Error( 'Invalid class name: ' + c );
}
}
// 避免写长长的 prototype,给 ClassList 定义一个 method 方法
// 用于给 ClassList 添加方法,使代码更有易读
ClassList.method = function( name, method ){
ClassList.prototype[ name ] = method;
}
// 在模块内部定义 ClassList 类,避免了污染全局环境
var ClassList = function( el ){
// 只有一个属性 el
this.el = el;
}
// 是否包含
ClassList.method( 'contains', function( c ){
// 要判断的 class 名不能是空字符串或者包含空格符的字符串
checkClass( c );
// 如果元素没有class属性或者class属性为空这直接返回 false
if( ! this.el.className ){
return false;
}
// 使用正则表达式判断是否包含指定 class 名
return this.el.className.search('\\b' + c + '\\b') !== -1;
} );
// 新增
ClassList.method( 'add', function( c ){
// 应该进行同样的错误判断
checkClass( c );
// 如果已经存在要新增 class, 则直接返回
if( this.contains( c ) ) return;
// 增加class
this.el.className += ' ' + c;
} );
ClassList.method( 'remove', function( c ){
// 应该进行同样的错误判断
checkClass( c );
// 通过正则表达式删除指定的 class
this.el.className = this.el.className.replace(new RegExp( '\\s*\\b' + c + '\\b', 'g' ), '' );
} );
ClassList.method( 'toggle', function( c ){
checkClass( c );
this.contains( c ) ? this.remove( c ) : this.add( c );
} );
ClassList.method( 'toString', function(){
return this.el.className;
} );
} ).call( this );

现在可以像这样使用我们封装的模块:
html

1
<a id="start" class="working disabled">Start</a>

javascript

1
2
3
4
5
6
var btn = document.getElementById( 'start' );
btnCls = classList( btn );
btnCls.contains( 'working' );
btnCls.remove( 'working' );
btnCls.add( 'textBtn' );
btnCls.toggle( 'disabled' );

转载请注明出处:http://yyblog.mocoer.com/2014/11/25/写自己的JavaScript工具-addClass:removeClass:hasClass/

关于 == 的误解

今天被人问:true == 3 的结果是?
我第一反应是:

  1. Javascript 是弱类型语言,“==”相等的概念很宽泛,这两个值都是真值,转换成 bool 类型后都为 true,应该相等;
  2. 用 if( true ) 和 if( 3 ) ,都会进入 if 代码块,应该相等;

于是随口回答: true。

实际结果是: false。

查了下犀牛,了解到以下两点,可以分别解释上面两条误解:

  1. == 运算符从不试图将操作数转化成布尔值,如果两个操作数比较,其中一个是 bool 类型,则会先将 bool 类型值转化成数字( true 转换为 1,false 转换为 0 ),然后再继续比较。
  2. 只有 if 语句会将假值(undefined, null, NaN, 0, ‘’)转换成false,将其他值(真值)转换为true,然后进行比较。

转载请注明出处:http://yyblog.mocoer.com/2014/11/05/关于==的误解/

Backbone/AngularJS 框架简单对比(TODO LIST为例)

BackboneAngularJS 都是目前非常流行的Web应用开发框架。他们有显著的不同,如果 Backbone 帮你架好了钢筋水泥,那 AngularJS 甚至做完了精装修。但并不说明谁更“好”,选择哪种框架开发,取决于项目的各方面需求和特点。

因此,这里通过两个实例(同样的应用,不同的框架),来大体了解到二者的特点。

本文 Demo :
Backbone 版

要点

  1. 构思 TODO LIST 应用
  2. 基于 Backbone 的 TODO LIST 应用
  3. 基于 AngularJS 的 TODO LIST 应用

1. 构思 TODO LIST 应用

TODO LIST 就是一个事项记录薄,基本功能包括:

  • a. 添加一条代办事项
  • b. 删去一条待办事项
  • c. 完成一条代办事项
  • d. 查看待办事项统计

一个典型的 CURD 类应用( AngularJS 在开发这类应用时占有绝对优势 )。
为了简单快捷,这里在 Bootstrap 框架下进行开发。

2. 基于 Backbone 的 TODO LIST 应用

首先是 Backbone 版 TODO LIST,页面中引入 backbone.js 和所有必要文件:

1
2
3
4
5
6
7
8
9
<link rel="stylesheet" href="/styles/bootstrap.css">
<link rel="stylesheet" href="/styles/todolist.css">
<!-- bootstrap 和 backbone 依赖 jquery -->
<script src="/scripts/jquery.js"></script>
<script src="/scripts/bootstrap.min.js"></script>
<!-- backbone 依赖 underscore -->
<script src="/scripts/underscore.js"></script>
<script src="/scripts/backbone.js"></script>
<script src="/scripts/backboneStorage.js"></script>

在我基本完成了程序时,发现 backbone 官方文档中提供了一个非常类似的例子…于是与之做了比较,对两个版本我认为好的点进行了整合,就有了下面看到的版本。

再来确认一下基本思路(代码注释中还会再次提示这些思路的具体体现):

  • a. 用户交互事件触发视图方法;
  • b. 视图方法改变模型或集合(如果有必要,最好是有两层方法,第一层用来处理用户交互事件,第二层才是真正改变模型或集合,这样就有可能提供接口来脱离用户交互直接操作应用);
  • c. 模型或集合的改变再触发视图的改变。

下面是具体实现:

html:

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
<div id="App">
<div class="header panel-heading">
<div class="itemCounter"><!-- 统计将在这里 --></div>
TODO LIST
</div>
<div class="content">
<input id="newTodo" class="form-control" type="text" placeholder="Remind me to do..." />
<div class="list list-group pannel-body"><!-- 事项视图将 prepend 在这 --></div>
</div>
</div>
<!-- 事项视图模板 -->
<script id="item-temp" type="text/html">
<div class="checkbox">
<label>
<input type="checkbox" <% if(done){ %> checked <% } %> />
<span class="title"><%= title %></span>
</label>
<span class="remove glyphicon glyphicon-remove"> </span>
</div>
</script>
<!-- 事项统计模板 -->
<script id="itemCounter-temp" type="text/html">
<span>Done : <%= done %></span>
<span>Undone : <%= undone %></span>
</script>

javascript :

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
// TODO LIST APP
// =============
var App = ( function(){
// A. 需要的 1 个模型:
// * Item ( 事项模型 )
// 定义 Item 模型 :
var Item = Backbone.Model.extend( {
// 事项的默认属性值
defaults : {
title : '', // 事项名称
done : false // 是否完成
},
// 切换事项完成状态
toggle : function(){
this.save( { done : ! this.get( 'done' ) } );
}
} );
// B. 需要的 1 个集合:
// * List ( 事项列表 )
// 定义 List :
var List = Backbone.Collection.extend( {
model : Item,
// 以 TODOLIST 作为本地存储的命名空间
localStorage: new Backbone.LocalStorage( 'TODOLIST' ),
// 完成事项列表
done : function(){
// 使用 Collection 的 where 接口可以非常方便的获取满足属性条件的模型列表
return this.where( { done : true } );
},
// 未完成事项列表
undone : function(){
return this.where( { done : false } );
}
} );
// C. 需要的 2 个视图:
// * ItemView ( 事项视图 )
// * AppView ( 应用视图,其实例将成为应用的对外接口 )
// 定义 ItemView :
var ItemView = Backbone.View.extend( {
tagName : 'a',
className : 'list-group-item', // Bootstrap 样式
template : _.template( $( '#item-temp' ).html() ),
// 想想基本思路a:用户交互事件触发视图方法
events : {
'click' : 'toggle',
'click .remove' : 'removeItem'
},
initialize : function(){
// 想想基本思路c:模型或集合的改变再触发视图的改变
this.listenTo( this.model, 'change', this.render );
this.listenTo( this.model, 'destroy', this.remove );
},
render : function(){
this.$el.html( this.template( this.model.toJSON() ) );
this.$el.toggleClass( 'done', this.model.get( 'done' ) );
return this;
},
// 想想基本思路b:视图方法改变模型或集合
toggle : function( e ){
this.model.toggle();
},
// 想想基本思路b:视图方法改变模型或集合
removeItem : function( e ){
this.model.destroy();
e.stopPropagation();
}
} );
// 定义 AppView :
// 该视图承载了主要的业务逻辑
var AppView = Backbone.View.extend( {
el : $( '#App' ),
// 想想基本思路a:用户交互事件触发视图方法
events : {
'keydown #newTodo' : 'addItemByInput'
},
itemCounterTemp : _.template( $( '#itemCounter-temp' ).html() ),
initialize : function(){
this.$input = this.$( '#newTodo' );
this.$list = this.$( '.list' );
this.$itemCounter = this.$( '.itemCounter' );
// 想想基本思路c:模型或集合的改变再触发视图的改变
this.listenTo( list, 'add', this.addItemView );
this.listenTo( list, 'reset', this.addItemByLocalStorage );
this.listenTo( list, 'all', this.render );
list.fetch();
},
render : function(){
var done = list.done().length,
undone = list.undone().length;
if ( ! list.length ) {
this.$itemCounter.hide();
}
else {
this.$itemCounter.show();
this.$itemCounter.html( this.itemCounterTemp( { done : done, undone : undone } ) );
}
},
addItemView : function( model ){
var view = new ItemView( { model : model } );
this.$list.prepend( view.render().el );
},
addItemByLocalStorage : function(){
list.each( this.addItem, this );
},
// 想想基本思路b:视图方法改变模型或集合
addItemByInput : function( e ){
if( this.$input.val() === '' || e.keyCode !== 13 ) return;
this.addItem( { title : this.$input.val() } );
this.$input.val( '' );
},
// 对外提供的接口
addItem : function( props ){
props && props.title && list.create( props );
},
// 对外提供的接口
removeItem : function( id ){
list.get( id ) && list.get( id ).destroy();
},
// 对外提供接口
toggleItem : function( id ){
list.get( id ) && list.get( id ).toggle();
}
} );
var list = new List;
var App = new AppView();
// 提供一些接口
return {
addItem : App.addItem,
removeItem : App.removeItem,
toggleItem : App.toggleItem
}
}() );

3. 基于 AngularJS 的 TODO LIST 应用

如果打算对 AngularJS 做深入的学习和开发实践,建议配置一个方便开发和测试的整体环境。详见这里

现在是 AngularJS 版的 TODO LIST,首先引入 AngularJS 和必要的文件,其中用到了之前写过的 cookieStorage.js。详见这里

1
2
3
4
5
6
7
<link href="bootstrap.css" rel="stylesheet">
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="angular.js"></script>
<!-- 引入了之前写的 cookie API,详见“本地存储”那一篇 -->
<script src="cookieStorage.js"></script>
<script src="bootstrap.js"></script>

接下来是应用的 HTML 结构,AngularJS 引入的模板引擎和丰富的指令,大大强化了 HTML 的能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div ng-app="todoListModule" ng-controller="TodoListController" id="App">
<div class="header panel-heading" style="padding: 10px 12px;">
<div ng-class='{hide : items.length == 0}' class="itemCounter">
<span>完成 : 0</span>
<span>未完成 : 0</span>
</div>
</div>
<div class="content">
<input ng-keydown="handleInput" ng-model="newTitle" id="newTodo" class="form-control" type="text" placeholder="新建事项..." autofocus="autofocus" />
<div class="list list-group pannel-body">
<a ng-repeat="item in items" ng-click="toggle( $index )" ng-class="{done : item.done}" class="list-group-item">
<div class="checkbox">
<label>
<input ng-model="item.done" type="checkbox">
<span class="title"></span>
</label>
<span ng-click="remove( $index, $event )" class="remove glyphicon glyphicon-remove"> </span>
</div>
</a>
</div>
</div>
</div>

最后是 JS 脚本,可以明显感觉到 AngularJS 帮我们做了很多事,如模型和视图的双向绑定,模块化的开发方式,实用的指令控制UI和事件,相比 Backbone,AngularJS 为开发者服务的更加周到:

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
// 定义 todoList 模块
var todoListModule = angular.module( 'todoListModule', [] );
// 定义一个 Items 服务,用于操作本地数据的 CURD
todoListModule.factory( 'Items', function(){
var memory = ( function(){
return localStorage || cookieStorage;
}() );
// 将作为 Items 服务的实例返回
var items = {};
// 查询全部事项
items.query = function(){
var d = memory.getItem( 'todoList' );
return ( d && JSON.parse( d ) ) || [];
}
// 插入新事项
items.add = function( data ){
var d = memory.getItem( 'todoList' );
d = ( d && JSON.parse( d ) ) || [];
d.unshift( data );
memory.setItem( 'todoList', JSON.stringify( d ) );
}
// 更新事项
items.update = function( index, data ){
var d = memory.getItem( 'todoList' );
if( d ){
d = JSON.parse( d );
d[ index ] = data;
memory.setItem( 'todoList', JSON.stringify( d ) );
}
}
// 删除事项
items.remove = function( index ){
var d = memory.getItem( 'todoList' );
if( d ){
d = JSON.parse( d );
d.splice( index, 1 );
memory.setItem( 'todoList', JSON.stringify( d ) );
}
}
return items;
} );
// 定义一个 ngKeydown 指令,用于处理用户的键盘按下事件
todoListModule.directive( 'ngKeydown', function(){
return {
link : function( scope, element, attrs ){
var handler = scope[ attrs.ngKeydown ];
element.bind( 'keydown', handler );
}
}
} );
// TodoList 控制器
function TodoListController( $scope, Items ){
// 通过 Items 服务读取本地数据
$scope.items = Items.query();
// 如果是从服务器存取,我们还要借助 Angular 的 $resouece 服务或者 $http 做一些额外的事情. 但是这个例子并没有涉及与服务器的交互。
// 完成的事项集
$scope.done = function(){
return _.where( $scope.items, { done : true } );
}
// 未完成的事项集
$scope.undone = function(){
return _.where( $scope.items, { done : false } );
}
// 切换事项状态
$scope.toggle = function( index ){
$scope.items[ index ].done = ! $scope.items[ index ].done;
Items.update( index, $scope.items[ index ] );
}
// 删除事项
$scope.remove = function( index, event ){
event.preventDefault();
$scope.items.splice( index, 1 );
Items.remove( index );
}
// 新事项名称
$scope.newTitle = '';
// 用户输入事件处理方法
$scope.handleInput = function( event ){
// 这里需要显式的调用 $scope.$apply 服务,以让 Angular 立即刷新模型和视图
$scope.$apply( function(){
var el = angular.element( event.target );
if( event.keyCode === 13 && $scope.newTitle.trim() != '' ){
var newItem = { title : $scope.newTitle , done : false };
$scope.items.unshift( newItem );
Items.add( newItem );
$scope.newTitle ='';
}
} );
}
}

转载请注明出处:http://yyblog.mocoer.com/2014/10/22/Backbone:AngularJS 框架对比(TODO LIST为例)/

写自己的JavaScript工具-本地存储

要点

  1. 了解 localStorage
  2. 了解 cookie
  3. 实现 memory 模块

1. 了解 localStorage

HTML5 的关于 Web 存储标准包含了关于 localStorage 对象。(和 sessionStorage 对象,它们的区别主要是在作用域和有效期,但 API 大同小异。)

localStorage 对象是客户端 Window 对象的属性,作为一种本地存储方案,我们需要关心它的三个方面:有效期,作用域和存取方法。

a. localStorage 有效期

localStorage 的有效期是永久性的,也就是说,除非用户做删除操作,否则通过 localStorage 存储的数据是永久保存在用户电脑上的。

b. localStorage 作用域

localStorage 的作用域是文档源级别的,文档原由协议、主机名、端口共同定义:

http://www.xxx.com
https://www.xxx.com
http://sub.xxx.com
http://sub.xxx.com:4000

上面这些都是不同文档源,相互不能共享 localStorage 的数据,只有同文档源的中的文档可以共享对方的 localStorage 数据。

localStorage 同时制约于浏览器厂商,不同浏览器之间不能共享。

c. localStorage 存取方法

localStorage 存取、删除数据的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 保存数据
// 方式1(直接定义属性值)
localStorage.username = 'yy';
// 方式2(通过 API)
localStorage.setItem( 'username', 'yy' );
// 获取数据
// 方式1(直接读取属性值)
localStorage.username;
// 方式2(通过 API)
localStorage.getItem( 'username' );
// 删除指定数据
// 方式1(通过 delete 操作符)
delete localStorage.username;
// 方式2(通过 API)
localStorage.removeItem( 'username' );
// 删除全部数据
localStorage.clear();

但是还是尽量使用 API 的方式,一个原因就是,当我们为了兼容不支持 localStorage 的浏览器时,可能需要通过 cookie 作为替代方案,这时我们只需要再针对 cookie 定义和 localStorage 同样的方法,就可以实现一个统一、兼容的本地存储方案。

还有需要了解的是 localStorage 目前只能存储字符串类型的数据。如果尝试存储数组或对象,客户端会先调用它们的 toString() 方法,然后再将转换成字符串的数据存储起来:

1
2
3
4
5
localStorage.arr = [1, 2, 3];
localSrorage.arr // => "1,2,3"
localStorage.obj = { username : 'yy' };
localStorage.obj // => "[object Object]"

所以如果要存取对象类型的数据,需要做一些编码、解码工作:

1
2
3
4
var obj = { username : 'yy' };
localStorage.obj = JSON.stringify( obj );
localStorage.obj // => "{"username":"yy"}"
obj = JSON.parse( localStorage.obj ); // => Object {username: "yy"}

在 localStorage 真正成为标准之前,cookie 作为本地存储的方案依然使用广泛,因为 cookie 的兼容性非常好。对于 cookie,我们依然是关心它的三个方面:有效期、作用域和存取方法。

cookie 的有效期是通过设置 max-age 完成的,以秒为单位。如果没有定义一个 cookie 的 max-age 值,则有效期默认为浏览器关闭时结束。一旦设置了 cookie 的有效期,则浏览器会将 cookie 数据存储在一个本地文件中,直到有效期结束,保存的数据会被删除(或直到用户手动清除了浏览器 cookie )。

默认情况下,cookie 的作用域是创建它的页面所在目录和子目录。但是可以通过设置 domain 来扩大他的作用域。比如 http://www.xxx.com/cates/index.html 页面创建了一个 cookie,那么 http://www.xxx.com/cates/list.html、http://www.xxx.com/cates/items/item.html 都可以访问该 cookie。而 http://www.xxx.com/index.html 就无法访问该 cookie。但是可以通过设置 cookie 的 domain 值为 ‘/‘ 来让该主机上的所有文件都可以访问该 cookie。

cookie 同时制约于浏览器厂商,即不同的浏览器有各自独立的 cookie。

存储 cookie

1
2
3
4
5
6
7
// 保存(修改)数据(一次只能设置一个值)
document.cookie = 'username=yy';
// 保存数据同时设定有效期
document.cookie = 'username=yy; max-age=3600'; // 有效期为1小时(3600秒)
// 保存数据同时设定有效期和作用域
// 有效期为1小时,可被a.xxx.com 和 b.xxx.com 共享
document.cookie = 'username=yy; max-age=3600; domain=.xxx.com; path=/';

读取 cookie

1
2
// 读取数据
document.cookie; // 将输出类似 data1=value1; data2=value2 名值对形式的字符串

删除 cookie

1
2
// 删除数据
document.cookie = 'username=value;max-age=0'; // 将要删除的cookie名指定为任意值,然后设置 max-age 为 0

3. 实现 memory 模块

定义 cookieStorage 模块,仿照 localStorage

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
// 首先需要实现一个 cookieStorage 模块(对象)
// 该模块有着与 localStorage 相同的方法(这里没有完全相同,只提供了主要的方法)
// 这样的话,我们就可以在支持 localStorage 和使用 cookieStorage 替代方案的
// 环境中统一使用我们最后定义的 memory 的模块了
var cookieStorage = ( function(){
// 在这里可以对 cookie 的有效期和作用域做配置
var config = {
maxAge : 24*3600, // 可以是秒数,也可以是 'auto',当设置为 'auto' 时,有效期直到浏览器关闭
path : '/'
}
// 获取现有的cookie
// 获取过程细节包装在一个函数中
// 避免了污染外层环境
// 函数即时执行并返回一个对象
// 该对象属性和值就是当前 cookie 的名值对,并将在模块内部被维护
var cookie = ( function(){
var cookie = {};
var all = document.cookie;
if( all === '' ){
return cookie;
}
var pairs = all.split( '; ' );
for( var i = 0, len = pairs.length;i < len; i += 1 ){
var pos = pairs[ i ].indexOf( '=' );
var key = pairs[ i ].substring( 0, pos );
var value = pairs[ i ].substring( pos + 1 );
value = decodeURIComponent( value );
cookie[ key ] = value;
}
return cookie;
}() );
// 设置cookie
var setItem = function( key, value ){
cookie[ key ] = value;
var _cookie = key + '=' + encodeURIComponent( value ); // 对值进行编码,避免出现非法字符
// 设置有效期
if( config.maxAge !== 'auto' ){
_cookie += '; max-age=' + config.maxAge;
}
// 设置作用域
if( config.path ){
_cookie += '; path=' + config.path;
}
// 建立cookie
document.cookie = _cookie;
}
// 获取cookie
var getItem = function( key ){
return cookie[ key ] || null;
}
// 删除cookie
var removeItem = function( key ){
if( key in cookie ){
delete cookie[ key ]; // 从内部维护的cookie对象中删除指定的属性
}
document.cookie = key + '=; max-age=0'; // 将cookie的有效期设置为0就是删除cookie
}
// 清除所有cookie
var clear = function(){
for( var key in cookie ){
document.cookie = key + '=;max-age=0';
}
cookie = {};
}
// 提供接口
return {
setItem : setItem,
getItem : getItem,
removeItem : removeItem,
clear : clear
}
}() );

定义 memory 模块

1
2
3
4
5
6
7
var memory = ( function(){
return localStorage || cookieStorage;
}() );
// 现在可以在支持或不支持 localStorage 的环境中像这样使用 memory
memory.setItem( 'username', 'yy' );
memory.getItem( 'username' ); // => yy

转载请注明出处:http://yyblog.mocoer.com/2014/10/09/写自己的JavaScript工具-本地存储/

写自己的JavaScript工具-模块

要点

  1. 为什么要用模块
  2. 模块的一般写法

1. 为什么要用模块

如果用一个词来概括为什么用模块,那就是:独立。

因为独立,所以它们可以载入到任何需要它们的地方,通过接口发挥它们的作用;因为独立,所以即便程序导入了不需要的模块,模块也不会对整个程序造成任何影响;因为独立,所以模块很多场景中得到重用;因为独立,所以它们都是完成健壮成熟的个体,一个特定功能的体现。

这些原因都是使用模块的原因,也是在开发任何程序时程序时考虑的。

模块的概念应该比类的概念更大,类是其中一种模块化的体现。

2. 模块的一般写法

一般的模块写法非常简单。为了做到定义最少的全局变量(不污染全局环境),保护私有属性和方法,模块最好以一个有私有命名空间的函数来构造(闭包在这里又起到了至关重要的作用):

1
2
3
4
5
6
7
8
9
10
var module = ( function namespace(){
/*
私有变量和方法
*/
reutrn {
/*
提供接口
*/
}
}() );

最外面的括号 () 表示这是一个函数表达式,而不是一个函数定义,里面是一个立即执行的函数,因为不是一个函数定义,所以没有真正定义一个叫 namespace 的函数,namespace 完全可以省略而使用一个匿名函数,这里只是为了让代码容易理解,表示这是一个私有命名空间,因此加上了这个前缀。在这个私有命名空间里,我们可以通过 var 定义变量和方法,不用担心它们污染全局环境,不用担心它们被外界访问修改。最后我们让函数返回一个对象,这个对象的属性是我们要暴露给外界的方法,以发挥模块的作用。当然,用户可能会重新定义或破坏这些暴露出来的方法,但是永远也不会影响到模块内部环境:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var uniqId = ( function namespace(){
// 私有变量
var privateVariable = 0;
// 私有方法
var getPrivateVariable = function(){
_autoIncrease();
return privateVariable;
},
resetPrivateVariable = function( value ){
privateVariable = 0;
},
// 下划线前缀表示这是一个真正私有的方法,不会暴露给外部
_autoIncrease = function(){
privateVariable += 1;
};
// 提供一些接口
return {
get : getPrivateVariable,
reset : resetPrivateVariable
};
}() );

可以这样使用刚刚定义的模块:

1
2
3
4
uniqId.get(); // => 1;
uniqId.get(); // => 2;
uniqId.reset();
uniqId.get(); // => 1;

你永远没有办法通过重写 reset 方法或者添加其他方法破坏 privateVariable 的值。

关于上面这个 uniqId 模块,另外一个实现可以见这里,里面有关于闭包技术的介绍,它是模块化编程中必须要理解和运用的,那个例子也可以理解为一个模块的实现,只不过功能更加单一化。

转载请注明出处:http://yyblog.mocoer.com/2014/10/03/写自己的JavaScript工具-模块/

写自己的JavaScript工具-跨域请求(JSONP)

要点

  1. 跨域 HTTP 请求
  2. JSONP(通过 script 标签发送 HTTP 请求)

1. 跨域 HTTP 请求

提到 XMLHttpRequest,我们都知道它可以发起 http 请求,并异步的执行回调,实现无刷新的用户体验。

1
2
3
4
5
6
7
8
9
10
11
// 封装一个简单的 get 请求,后面会用它来做测试
var get = function( url, callback ){
var request = new XMLHttpRequest();
request.open( 'GET', url );
request.onreadystatechange = function(){
if( request.readyState === 4 && request.status === 200 && callback ){
callback( request );
}
}
request.send( null );
}

但为了安全,XMLHttpRequest 对象仅允许发起与文档有相同服务器的 HTTP 请求(所谓同源策略)。

但有时我们需要这样的合理需求:拿“Apple天猫旗舰店”来举例,在IPhone 5s 的产品页 http://detail.tmall.com/item.htm?id=36641396665. 当用户要查看产品评价(点击“累计评价” tab 标签)时,页面需要发送 Ajax 请求来获取当前产品的评价信息,而请求地址是 http://rate.tmall.com/list_detail_rate.htm?itemId=36641396665. 是一个跨域请求。(发起请求的服务器:detail.tmall.com,被请求的服务器:rate.tmall.com,它们是不同源。)

我们做这个测试,浏览器打开 IPhone 5s 产品页:http://detail.tmall.com/item.htm?id=36641396665. 打开浏览器开发者工具,在控制台中输入以下程序(先定义上面的 get 函数)。

1
get( 'http://rate.tmall.com/list_detail_rate.htm?itemId=36641396665' );

控制台会返回一个错误提示:
XMLHttpRequest cannot load http://rate.tmall.com/list_detail_rate.htm?itemId=36641396665. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://detail.tmall.com‘ is therefore not allowed access.

即当前页面无权载入请求的文档,这就是同源策略导致的,但这种请求本身是完全合理的。

对于这种合理的跨域请求,我们需要借助 JSONP 技术。

2. JSONP(通过 script 标签发送 HTTP 请求)

script 标签并未受同源策略的限制,我们可以通过设置它的 src 属性,载入任何服务器上的 JavaScript 脚本:

1
2
<!-- 比如在自己的服务器上通过 CDN 加速点加载 jquery,当然这要建立在对方服务器可信的情况下 -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

也就是说我们需要动态的生成 script 标签,并设置 src 属性为要请求的地址,最后将设置好 script 标签添加到页面中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 新建一个 script 标签
var script = document.createElement('script');
// 设置 script 标签的 src 属性为要请求的地址,同时加一个 callback 参数
// 这个参数名 callback 非常关键,这个参数需要后台程序的支持才能够识别,当后台程序看到这个参数时
// 后台程序需要知道这是一个 JSONP 请求
// 这时,后台程序需要用这个参数的值( jsonpCallback )来包装返回的数据并返回 JSON 字符串
// 这个包装后的字符串看上去就像是将数据结果带入了函数 jsonpCallback 一样
// 当客户端获取到包装后的字符串时,script 标签会立即解析执行这个语句
// 而程序中我们刚好定义了 jsonpCallback 函数
// 所以我们可以用 jsonpCallback 函数来处理获取的数据(在这里只是将数据 console.log() 出来)
script.src = "http://rate.tmall.com/list_detail_rate.htm?itemId=36641396665&callback=callback";
var callback = function( response ){
console.log( response );
}
document.body.appendChild( script );

在刚才的页面控制台输入上面的程序,我们得到了想要的结果。关于地址中的 callback 参数,不同服务器可能叫法不同,常见的还有 jsonp,当然也可以根据需要自己在后台程序中定义。

最后做一些封装工作(用到了 uniqId 函数,详见这里 ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var getJSONP = function( url, callbackName, callback ){
var jsonpId = 'jsonp' + uniqId(),
jsonpName = 'getJSONP.' + jsonpId;
var script = document.createElement( 'script' );
script.src = url.indexOf( '?' ) === -1 ?
url + '?' + callbackName + '=' + jsonpName :
url + '&' + callbackName + '=' + jsonpName;
getJSONP[ jsonpId ] = function( response ){
try {
callback( response );
}
finally {
delete getJSONP[ jsonpId ];
script.parentNode.removeChild( script );
}
};
document.body.appendChild( script );
};

现在可以像这样使用 getJSONP:

1
2
3
4
5
getJSONP(
'http://rate.tmall.com/list_detail_rate.htm?itemId=36641396665',
'callback',
function( response ){ console.log( response ); }
);

转载请注明出处:http://yyblog.mocoer.com/2014/10/02/写自己的JavaScript工具-跨域请求( JSONP )/

写自己的JavaScript工具-max/min

要点

  1. 关于 arguments 对象
  2. 利用 arguments 实现 max/min 函数 ( 不定实参函数( varargs function ))

1. 关于 arguments 对象

JavaScript 中的每个函数都“附赠”了两个局部变量:this 和 arguments。

arguments 是函数的实参对象,它是一个类数组,不是真正的数组,没有数组通常都有的方法,但它有 length 属性和以数字为索引的一组元素:

1
2
3
4
5
6
7
8
9
10
11
12
var f = function( x ){
console.log( x, arguments[ 0 ], arguments[ 1 ], arguments[ 2 ] );
console.log( arguments.length );
console.log( arguments.slice );
arguments[ 0 ] = -1;
console.log( x );
}
f( 1, 2 );
// => 1, 1, 2, undefined
// => 2
// => undefined 说明 arguments 不是真正的数组
// => -1 说明 arguments[ 0 ] 和 x 指代同一个值

上面的程序基本展示了 arguments 属性的一些特点。

除此之外,arguments 对象还有两个属性:caller 和 callee,前者指代调用当前函数的函数,后者指代当前正在执行的函数。callee 在某些递归调用中很有用:

1
2
3
4
5
6
7
8
var f = function( x ){
if( x > 1 ){
return x + arguments.callee( x - 1 );
}else{
return 1;
}
}
f(5); // => 15

2. 利用 arguments 实现 max/min 函数 ( 不定实参函数( varargs function ))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var max = function(){
var max = -Infinity; // 或 Number.NEGATIVE_INFINITY
for( var i = 0, len = arguments.length; i < len; i++ ){
max = arguments[ i ] > max ? arguments[ i ] : max;
}
return max;
}
var min = function(){
var min = Infinity; // 或 Number.POSITIVE_INFINITY
for( var i = 0, len = arguments.length; i < len; i++ ){
min = arguments[ i ] < min ? arguments[ i ] : min;
}
return min;
}
max(3,4,2,7,1); // => 7
min(3,4,2,7,1); // => 1

转载请注明出处:http://yyblog.mocoer.com/2014/10/01/写自己的JavaScript工具-max:min/