本文深入解析JavaScript中的链式调用机制,通过改造构造函数与原型方法,展示如何实现支持链式调用的对象。文章涵盖工厂方法、安装器模式及回调技术,解决命名冲突与取值器中断链接的问题,提供完整的代码示例与最佳实践建议。
JavaScript链式调用概述
本文通过具体实例剖析JavaScript中的链式调用技术,旨在为开发者提供清晰的实现思路与参考代码。
基础函数与链式调用改造
对于$函数,大家应当已经非常熟悉。该函数通常用于获取单个HTML元素或返回一个包含多个HTML元素的集合,具体用法如下所示:
function$(){
var elements = [];
for(vari=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(){
//use private class
function _$(els){
this.elements = [];
for(vari=0,len=els.length;i<len;i++){
var element = els[i];
if(typeof element ===”string”){
element = document.getElementById(element);
}
this.elements.push(element)
}
}
//The public interface remains the same.
window.$ = function(){
return new _$(arguments);
}
})();
原型对象与链式实现
鉴于所有对象均会继承其原型对象的属性与方法,我们只需让定义在原型对象中的各方法返回调用该方法的实例对象引用,即可实现对这些方法的链式调用。明确这一思路后,接下来便可在_$这一私有构造函数的prototype对象中添置相关方法,以达成链式调用的功能。
(function(){
//use private class
function _$(els){
//..省略之前上面的代码
}
_$.prototype = {
each:function(fn){
for(var i=0,len=this.elements.length;i<len;i++){
fn.call(this,this.elements[i]);
}
return this;
},
show:function(prop,val){
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;
}
};
//The public interface remains the same.
window.$ = function(){
return new _$(arguments);
}
})();
安装器模式与命名冲突解决
倘若某个库或框架已定义了$函数,为避免冲突,一种简单的处理方案是在源码中为$函数另取一个名称。但若源码来自现有库,每次获取更新版本后都需重新修改名称,显然并非良策。更为优渥的解决方案是引入如下所示的安装器:
window.installHelper = function(scope, interface) {
scope[interface] = function() {
return new _$(arguments);
}
};
用户可通过以下方式使用该安装器:
installHelper(window, '$');
$('example').show();
以下展示了一个更为复杂的示例,演示了如何将此功能添加至预先定义好的命名对象中:
// Define a namespace without overwriting it if it already exists.
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(e) {
get(this).addClass('hello');
});
})();
取值器与回调技术的应用
并非所有场景都适合将方法串联执行。链式调用在赋值器方法中表现优异,但对于取值器方法,用户往往期望其返回具体数据而非this。若将链式调用作为首要目标,并力求所有方法使用方式保持一致,则可通过回调技术来获取所需数据。以下提供两个示例:其中API类采用普通取值器(这将中断调用链),而API2类则采用回调方法:
// Accessor without function callbacks: returning requested data in accessors.
window.API = window.API || {};
API.prototype = function() {
var name = 'Hello world';
// Privileged mutator method.
setName: function(newName) {
name = newName;
return this;
},
// Privileged accessor method.
getName: function() {
return name;
}
}();
// Implementation code.
var o = new API;
console.log(o.getName()); // Displays 'Hello world'.
console.log(o.setName('Meow').getName()); // Displays 'Meow'.
// Accessor with function callbacks.
window.API2 = window.API2 || {};
API2.prototype = function() {
var name = 'Hello world';
// Privileged mutator method.
setName: function(newName) {
name = newName;
return this;
},
// Privileged accessor method.
//通过把函数作为参数传入
getName: function(callback) {
callback.call(this, name);
return this;
}
}();
// Implementation code.
var o2 = new API2;
o2.getName(console.log).setName('Meow').getName(console.log);
// Displays 'Hello world' and then 'Meow'.
如需了解更多JavaScript相关内容,建议读者参阅本站专题:《JavaScript数据结构与算法技巧总结》、《JavaScript常用函数技巧汇总》、《JavaScript操作DOM技巧总结》、《JavaScript页面元素操作技巧总结》及《JavaScript错误与调试技巧总结》。
希望上述阐述能对各位的JavaScript程序设计工作有所助益。