方法的链式调用
链式调用 其实只不过是一种语法招数,它能让你通过重用一个初始操作来达到用少量代码表达复杂操作的目的。
这种技术包含两部分:
- 创建代码HTML元素的对象的工厂
- 以及一批对这个HTML元素执行某些操作的方法
调用链的结构
$ 函数,它通常返回一个HTML元素或者一个HTML元素的集合,如:
function $(){
var elements = [];
for(var i = 0, len = arguments.length; i < len; ++i){
var element = arguments[i];
if(typeof element === "string"){
element = document.getElementById(element);
}
if(arguments.length === 1){
return element;
}
elements.push(element);
}
return elements;
}
但是,把这个函数改造成一个构造器,把那些元素作为数组保存在一个实例属性中,并把所有定义在构造器函数的 prototype属性指向对象中的方法都返回用以调用方法的那个实例的引用,那么它就具有了进行链式调用的能力。
首先把这个 $函数改为一个工厂方法,它负责创建支持链式调用的对象。这个函数应该能接受元素数组形式的参数,以便我们能使用与原来一样的公共接口。
(function(){
// 使用一个私有的类
function _$(els){
this.elements = [];
for(var i = 0, len = els.length; i < len; i++){
var element = els[i];
if(typeof element === "string"){
element = document.getElementById(element);
}
this.elements.push(element);
}
}
// 公开接口保持一样
window.$ = function(){
return new _$(arguments);
}
})();
由于所有对象都会继承其原型对象的属性和方法,所以我们可以让定义在原型对象中的那几个方法都返回用以调用发发的实例对象的引用,这样就可以对那些方法实现链式调用。想好这一点,现在就动手在 _$这个私用构造函数的 prototype对象中添加方法,以便实现链式调用:
(function(){
function _$(els){
this.elements = [];
for(var i = 0, len = els.length; i < len; i++){
var element = els[i];
if(typeof element === "string"){
element = document.getElementById(element);
}
this.elements.push(element);
}
}
_$.prototype = {
each : function(fn){
for(var i = 0, len = this.elements.length; i < len; i++){
fn.call(this, this.elements[i]);
}
return this;
},
setStyle : function(prop, val){
this.each(function(el){
el.style[prop] = val;
});
return this;
},
show : function(){
var that = this;
this.each(function(el){
that.setStyle("display", "block");
});
return this;
},
addEvent : function(type, fn){
var add = function(el){
if(window.addEventListener){
el.addEventlistener(type, fn, false);
} else if(window.attachEvent){
el.attachEvent("on" + type, fn);
}
};
this.each(function(el){
add(el);
});
return this;
}
};
window.$ = function(){
return new _$(arguments);
};
})();
设计一个支持方法链式调用的 JavaScript库
要对那个私用的 _$构造函数进行扩充,把这些东西都包括进去,那么其伪码大致是这样:
// 包含语法糖来帮助发展我们的接口
Function.prototype.method = function(name, fn){
this.prototype[name] = fn;
return this;
};
(function(){
function _$(els){
...
}
/*
Events
* addEvent
* getEvent
*/
_$.method("addEvent", function(type, fn){
// ...
}).method("getEvent", function(type, fn){
// ...
})
/*
DOM
* addClass
* removeClass
* replaceClass
* hasClass
* getStyle
* setStyle
*/
.method("addClass", function(className){
// ...
}).method("removeClass", function(className){
// ...
}).method("replaceClass", function(oldClass, newClass){
// ...
}).method("hasClass", function(className){
// ...
}).method("getStyle", function(){
// ...
}).method("setStyle", function(){
// ...
})
/*
Ajax
*/
.method("load", function(url, method){
// ...
});
window.$ = function(){
return new _$(arguments);
};
})();
如果某个API已经定义了一个 $函数,那么我们的这个库将会被改写。有个简单的办法就是在源代码中为 $ 另取一个名字。但是,如果你是一个从现有的源代码库中取得的源代码,那么每次版本更新版本后,每次都得更改那个函数的名字,这个解决方案并不理想。在这种情况下,更好的解决办法是像下面这样添加一个安装器(install):
Function.prototype.method = function(name, fn){
// ...
};
(function(){
function _$(els){
// ...
}
_$.method("addEvent", function(type, fn){
// ..
});
window.installHelper = function(scope, interface){
scope[interface] = function(){
return new _$(arguments);
}
};
})();
用户可能会这样使用它,
installHelper(window, "$");
下面是一个更复杂的例子,它演示了如何把这种功能添加到一个事先定义好的命名空间对象中:
// 定义一个命名空间,如果已经存在不会重写
window.com = window.com || {};
com.example = com.example || {};
com.example.util = com.example.util || {};
installHelper(com.example.util, "get");
(function(){
var get = com.example.util.get;
get("example").addEvent("click", function(){
get(this).addClass("hello");
});
})();
小结
在JavaScript中对象是作为引用被传递的。所以你可以让每个方法都传回对象的引用。
如果让一个类的每个方法都返回this值,那么它就成了一个支持方法的链式调用的类。