这一章要实现的就是jQuery的那种链式调用,例子:
$(this).setStyle('color', 'green').show();
一:调用链的结构:
首先我们来看一下最简单的$()函数的实现:
function $() {
var elements = [];
for (var i = , len = arguments.length; i < len; ++i) {
var element = arguments[i];
if (typeof element == 'string') {
element = document.getElementById(element);
}
if (arguments.length == ) {
return element;
}
elements.push(element);
}
return elements;
}
如果我们要实现$().method()这样的使用方法,想到应该就是$()函数有返回值,并且返回的是一个对象。要实现前面的所说的,最好的方法就是$()里面应该有个类可以让我们new一个对象出来,所以上面的代码就变成下面的样子:
(function() {
// Use a private class.
function _$(els) {
this.elements = [];
for (var i = , 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() {
function _$(els) {
// ...
}
_$.prototype = {
each: function(fn) {
for ( var i = , 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);
};
})();
到这里,我们就可以大概的使用一下了:
$('test-1', 'test-2').show().
setStyle('color', 'red').
addEvent('click', function(e) {
$(this).setStyle('color', 'green');
});
二:设计一个支持方法链式调用的JavaScript库:
一般的js库里面都包含着事件、DOM、Ajax这几个重要的模块,那模仿一下:
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(e) {
// ...
}).
/* 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(prop) {
// ...
}).method('setStyle', function(prop, val) {
// ...
}).
/* AJAX * load. Fetches an HTML fragment from a URL and inserts it into an element. */
method('load', function(uri, method) {
// ...
});
window.$ = function() {
return new _$(arguments);
});
})();
其实用的还是上面的方法。
不过,注意到最上面的几行代码了吗,我记得第一章说过,这样的话让添加prototype方法也使用链式调用玩起来了。。
还有,倒数第四行那里,我们总是把函数赋予给window.$,这样的话与一些使用$做命名空间的库是有冲突的,例如和jQuery有冲突~,那换命名空间吧,下面就实现一个安装器来方便改变命名空间:
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绑定相应的命名空间之后才能使用。
使用方法如下:
installHelper(window, '$'); $('example').show();
这样就等于把函数赋值给了window.$了。
三:使用回调从支持链式调用的方法获取数据:
上面说的那些链式方法都是返回this的,如果方法本身要返回值了?那么和返回this不就冲突了吗?所以对于这种情况,我们要想出一种方法,那就是对于这种情况,我们传入一个回调函数,来调用返回来的数值,之后还是要返回this的~
例子:
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;
}
}();
上面代码只要看getName哪里就可以了,传入了一个回调函数,然后取出值来直接调用,之后还是返回this,就不破坏链式调用了。
上面代码的一个使用方法:
var o2 = new API2;
o2.getName(console.log).setName('Meow').getName(console.log);