这一章要实现的就是jQuery的那种链式调用,例子:
$(this).setStyle('color', 'green').show();
一:调用链的结构:
首先我们来看一下最简单的$()函数的实现:
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; }
如果我们要实现$().method()这样的使用方法,想到应该就是$()函数有返回值,并且返回的是一个对象。要实现前面的所说的,最好的方法就是$()里面应该有个类可以让我们new一个对象出来,所以上面的代码就变成下面的样子:
(function() { // Use a private class. 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); } } // The public interface remains the same. window.$ = function() { return new _$(arguments); }; })();
此外我们的对象应该要有方法可以调用啊,那就定义在prototype上吧,然后代码就变成下面那样了:
(function() { 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; }, 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);