如果你了解Jquery,你一定为它的chaining的便利而折服。而它的原理,其实也很简单,不过是每个方法都返回this对象而已。如下:
(
function ($) {
function _$(elements) {
this._elements = [];
for (
var i = 0; i < elements.length; i++) {
if (
typeof (elements[i]) == "string") {
this._elements.push(document.getElementById(elements[i]));
}
else {
this._elements.push(elements[i]);
}
}
}
_$.prototype = {
each:
function (fn) {
if (arguments.length == 1) {
for (
var i = 0; i <
this._elements.length; i++) {
fn.call(
this._elements[i]);
}
}
else {
var argumentArr = [];
for (
var i = 1; i < arguments.length; i++) {
argumentArr[i - 1] = arguments[i];
}
for (
var i = 0; i <
this._elements.length; i++) {
fn.apply(
this._elements[i], argumentArr);
}
}
return
this;
},
setStyle:
function (attribute, attributeValue) {
var _setStyle =
function (attribute, attributeValue) {
this.style[attribute] = attributeValue;
};
return
this.each(_setStyle, attribute, attributeValue);
},
display:
function () {
return
this.setStyle("display", "block");
},
hide:
function () {
return
this.setStyle("display", "none");
},
addEvent:
function (type, fn) {
var _addEvent =
function (type, fn) {
if (window.addEventListener) {
this.addEventListener(type, fn,
false);
}
else
if (window.attachEvent) {
this.attachEvent('on' + type, fn);
}
};
return
this.each(_addEvent, type, fn);
},
removeEvent:
function (type, fn) {
var _removeEvent =
function (type, fn) {
if (window.removeEventListener) {
this.removeEventListener(type, fn,
false);
}
else
if (window.detachEvent) {
this.detachEvent('on' + type, fn);
}
};
return
this.each(_removeEvent, type, fn);
}
};
window[$] =
function () {
return
new _$(arguments);
};
} ("$"));
是不是一个简单版的Jquery? 它不过是先在闭包中先定义一个内部类,同时类的构造器传值一个或多个dom元素的id或者类实体对象(这里只是简写,并没有Juery那么复杂)。然后通过扩展内部类的prototype属性,实现一系列方法。在这堆方法里,最核心的是each方法了,它完美的使用了call和apply,然后有容乃大的显式接受一个funciton指针参数。另外,还有1个类似于Jquery的方式就是闭包最外层传递的$参数值了。 实际上,它可以是任意的合法js变量名。
看看调用方式吧,有它一切你都会懂得:
<
html
>
<
head
>
</
head
>
<
body
>
<
div
id
="div1"
>this is div1.
</
div
>
<
div
id
="div2"
>this is div2.
</
div
>
<
script
type
="text/javascript"
src
="chaining.js"
></
script
>
<
script
type
="text/javascript"
>
var
sayHello
=
function
() {
alert(
'
hello, I am is
'
+
this
.id);
}
$(
'
div1
'
,
'
div2
'
).setStyle(
"
color
"
,
"
red
"
).addEvent(
"
click
"
, sayHello);
$(
'
div2
'
).setStyle(
"
color
"
,
"
yellow
"
).removeEvent(
"
click
"
, sayHello);
</
script
>
</
body
>
</html>
关键点在于以$开头的那2行啊,是不是果然很Jquery? :)
而如果要作为一个好的Js Library,仅仅这么做,是不是还不够完美?比如,如果我想不用$作为限定操作符,将控制权完全交给Library用户。改进如下:
(
function () {
function _$(elements) {
this._elements = [];
for (
var i = 0; i < elements.length; i++) {
if (
typeof (elements[i]) == "string") {
this._elements.push(document.getElementById(elements[i]));
}
else {
this._elements.push(elements[i]);
}
}
}
_$.prototype = {
each:
function (fn) {
if (arguments.length == 1) {
for (
var i = 0; i <
this._elements.length; i++) {
fn.call(
this._elements[i]);
}
}
else {
var argumentArr = [];
for (
var i = 1; i < arguments.length; i++) {
argumentArr[i - 1] = arguments[i];
}
for (
var i = 0; i <
this._elements.length; i++) {
fn.apply(
this._elements[i], argumentArr);
}
}
return
this;
},
setStyle:
function (attribute, attributeValue) {
var _setStyle =
function (attribute, attributeValue) {
this.style[attribute] = attributeValue;
};
return
this.each(_setStyle, attribute, attributeValue);
},
display:
function () {
return
this.setStyle("display", "block");
},
hide:
function () {
return
this.setStyle("display", "none");
},
addEvent:
function (type, fn) {
var _addEvent =
function (type, fn) {
if (window.addEventListener) {
this.addEventListener(type, fn,
false);
}
else
if (window.attachEvent) {
this.attachEvent('on' + type, fn);
}
};
return
this.each(_addEvent, type, fn);
},
removeEvent:
function (type, fn) {
var _removeEvent =
function (type, fn) {
if (window.removeEventListener) {
this.removeEventListener(type, fn,
false);
}
else
if (window.detachEvent) {
this.detachEvent('on' + type, fn);
}
};
return
this.each(_removeEvent, type, fn);
}
};
window.installHelper =
function (scrope, interfaceName) {
scrope[interfaceName] =
function () {
return
new _$(arguments);
}
};
} ());
调用方式为:
window.installHelper(window, "$");
这里的两个参数可以按照需要传值。比如,如果你定义了一个com命名空间,然后想将上述chaining类库绑定到com.util上,那么执行:
window.installHelper(window.com, "util");
即可了。使用时,实例如下:
var $ = com.util;
$('div1', 'div2').setStyle("color", "red").addEvent("click", sayHello);
$('div2').setStyle("color", "yellow").removeEvent("click", sayHello);
注意,上述$非必须,你可以使用其他任意变量名,我只是偷懒而已。
是不是觉得很闷骚? 呵呵,这还不够撒!为了说明问题,我列举一个稍微有点极端的简单例子:
function Api(){
this.name = "Hello world";
}
Api.prototype = (
function () {
return {
setName:
function (name) {
this.name = name;
return
this;
},
getName:
function () {
return
this.name;
},
getMessage:
function () {
console.log(
this.name);
return
this;
}
};
})();
var api =
new Api();
api.getMessage().setName("changed").getMessage().getName();
在setName(name)方法里返回对象自身,我们可以接受。但是,在访问器getName()里呢?我们要的是对象属性值,不是对象自身,所以只返回name值。但是,调用getName()后,chaining就断了! 怎么办?
借鉴于Jsonp,我们也可搞个回调函数玩玩:
function Api2(name) {
this.name = "Hello World";
}
Api2.prototype = (
function () {
return {
setName:
function (name) {
this.name = name;
return
this;
},
getName:
function (callback) {
callback.call(
this,
this.name);
return
this;
},
getMessage:
function () {
console.log(
this.name);
return
this;
}
};
})();
var api2 =
new Api2();
var callback =
function(name){
console.log(name);
};
api2.getMessage().setName("changed").getMessage().getName(callback).setName("changed2").getMessage();
至此就可以无限的chaining了!
本文和所有其他本人的Pro Javascript Design Pattern系列全部文章均基于Pro Javascript Design Pattern的思想,并修正书中的一些源码错误。 源码download