addEvent($('.example'), 'click', function () { // Without chaining: $(this).hide(); setStyle(this, 'color', 'green'); $(this).show(); // With chaining; $(this).hide().setStyle('color', 'green').show(); });
$函数一般会返回一个HTML 元素(的集合):ajax
function $() { var eles = []; for (var i = 0, len = arguments.length; i < len; ++i) { var ele = arguments[i]; if (typeof ele === 'string') { ele = document.getElementById(ele); } if (arguments.length === 1) { return ele; } eles.push(ele); } return eles; }
若是把这个函数改形成一个构造器,把那些元素做为数组保存在一个实例属性中,并让全部定义在构造器函数的 prototype 属性所指对象中的方法都返回用来调用方法的那个实例的引用,那么它就具备链式调用的能力.
作一下改进:首先把$函数改为一个工厂方法,负责建立支持链式调用的对象,这个函数应该能接受元素数组形式的参数,因此咱们可以使用和原来同样的公有接口:数组
(function () { // Use a private class. function _$(els) { this.eles = []; for (var i = 0, len = els.length; i < len; ++i) { var ele = els[i]; if (typeod ele === 'string') { ele = document.getElementById(ele); } this.eles.push(ele); } } // The public interface remains the same. window.$ = function () { return new _$(arguments); }; })();
因为全部对象都会继承其原型对象的属性和方法,因此咱们可让定义在原型对象中的那几个方法都返回用以调用方法的实例对象的引用,这样就能够对哪些方法进行链式调用.如今在_$这个私有构造函数的 prototype 对象中添加方法:浏览器
(function () { function _$(eles) { // ... } _$.prototype = { each: function (fn) { for (var i = 0, len = this.eles.length; i < len; ++i) { fn.call(this, this.eles[i]); } return this; }, hide: function (0 { var that = this; this.setStyle('display', 'none'); }); setStyle: function (prop, val) { this.each(function (ele) { ele.style[prop] = val; }); return this; }, show: function (0 { var that = this; this.setStyle('display', 'block'); }); return this; addEvent: function(type, fn) { var add = function (ele) { if (window.addEventListener) { ele.addEventList(type, fn, false); } else if (window.attachEvent) { ele.attachEvnet('on' + type, fn); } }; this.each(function (el) { add(el); }); return this; } }; window.$ = function () { return new _$(arguments); }; })();
每一个方法的最后一行return this;
会讲调用方法对象传给调用链上的下一个方法.
jQuery 即是这样,window 对象或者某个 HTML 元素是调用链的锚点,多有操做都挂系在上面.异步
链式调用很适合于赋值器方法,可是对于取值器方法,并不但愿方法返回 this.不过使用回调技术能够返回你想要的数据而不是 this.ide
// Accessor without function callbacks: returning requested data in accessors. window.API = window.API || function () { var name = 'Hello world'; // Privilleged mutator this.setName = function(newName) { name = newName; return this; }; // Privileged accessor method. this.getName = function () { return name; }; }; // Implementation code var o = new API; console.log(o.getName()); // Displays 'Hello world'. console.log(o.setName('nanci').getName()); // Display 'nanci' // Accessor with function callbacks. window.API2 = window.API2 || function () { var name = 'Hello world'; // Privilleged mutator this.setName = function(newName) { name = newName; return this; }; // Privileged accessor method. this.getName = function (callback) { callback.call(this, name); return this; }; } // Implementation code var o2 = new API2; o2.getName(console.log).setName('nanci').getName(console.log); // Displays 'Hello world' and then display 'nanci'
使用链式调用能够避免屡次重复使用一个对象变量,减小代码量.
若是想让类的接口保持一致,让取值器像赋值器那样也支持链式调用,那么可使用回调.模块化
----------another part----------函数
若是你想开几个自行车商店,每一个店都有几种型号的自行车出售,用一个类表示:post
// BicycleShop class. var BicycleShop = function () {}; BicycleShop.prototype = { sellBicycle: function(model) { var bicycle; switch(model) { case 'The Speedter': bicycle = new Speedter(); break; case 'The Lowrider': bicycle = new Lowrider(); break; case 'The Comfort Cruiser': default: bicycle = new ComfortCruiser(); } Interface.ensureImplements(bicycle, Bicycle); bicycle.assemble(); bicycle.wash(); return bicycle; } };
sellBicycle 方法根据所要求的自行车型号用 switch 语句建立一个自行车的实例.各类型号的自行车实例能够互换使用,由于他们都实现了 Bicycle 接口(接口在工厂中很重要,若是不对对象进行某种类型检查以其确保其实现了必须的方法,那么工厂模式并不能带来什么好处).ui
// The Bicycle interface. var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair']); // Speedster class. var Speedster = function () { // implement Bicycle ... }; Speedster.prototype = { assemble: function () { ... }, wash: function () { ... }, ride: function () { ... }, repair: function () { ... } };
要出售某种型号自行车,只须要调用 sellBicycle 方法便可:this
var californiaCruisers = new BicycleShop(); var yourNewBike = californiaCruisers.sellBicycle('The Speedster');
若是你想在供货目录中加入一款新车型,更好的解决办法是把 sellBicycle 方法中"建立新实例"这部分工做转交给一个简单工厂对象.
// BicycleFactory namespace. var BicycleFactory = { createBicycle: function (model) { var bicycle; switch(model) { case 'The Speedter': bicycle = new Speedter(); break; case 'The Lowrider': bicycle = new Lowrider(); break; case 'The Comfort Cruiser': default: bicycle = new ComfortCruiser(); } Interface.ensureImplements(bicycle, Bicycle); return bicycle; } };
BicycleFactory 是一个单体,用来把 createBicycle 方法封装在一个命名空间中,这个方法返回一个实现了 Bicycle接口的对象,而后能够对其进行组装和清洗:
// BicycleShop class, improved. var BicycleShop = function () {}; BicycleShop.prototype = { sellBicycle: function (model) { var bicycle = BicycleFactory.createBicycle(model); bicycle.assemble(); bicycle.wash(); return bicycle; } };
这个 BicycleFactory 对象能够供各类类用来建立新的自行车实例.有关可供车型的全部信息都集中在一个地方管理,因此添加更多车型很容易:
// BicycleFactory namespace, with more models. var Bicycle: function (model) { var bicycle; switch (model) { case 'The Speedster': bicycle = new Speedster(); break; case 'The Lowrider': bicycle = new Lowrider(); break; case 'The Flatlander': bicycle = new Flatlander(); break; case 'The ComfortCruiser': bicycle = new ComfortCruiser(); } Interface.ensureImplements(bicycle, Bicycle); return bicycle; }
这是一个简单工厂的例子,他把成员对象的建立工做交给一个外部对象,这个外部对象能够是一个简单的命名空间,也能够是一个类的实例.
用 Ajax 技术发起异步请求是如今 Web 开发的一个常见任务.用于发起请求的对象是某种类的实例,具体是哪一种类取决于用户的浏览器.若是代码中须要屡次执行 ajax 请求,那么能够把建立这种对象的代码提取到一个类中,并建立一个包装器来包装在实际发起请求时所要经历的一系列步骤,简单工厂很是适合该场合,根据浏览器特性生成一个 XMLHttpRequest 或者 ActiveXObject 实例.
// AjaxHandler interface. var AjaxHandler = new Interface('AjaxHandler', ['request', 'createXhrObject']); // SimpleHandler class. var SimleHandler = function () {}; // implements AjaxHandler SimpleHandler.prototype = { request: function (method, url, callback, postVars) { var xhr = this.createXhrObject(); xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; (xhr.status === 200) ? callback.success(xhr.responseText) : callback.failure(xhr.status); }; xhr.open(method, url, true); if (method !== 'POST') { postVars = null; } xhr.send(postVars); }, createXhrObject: function () { // Factory method. var methods = [ function () { return newXMLHttpRequest(); }, function () { return new ActiveXObject('Msxml2.XMLHTTP'); }, function () { return new ActiveXObject('Microsoft.XMLHTTP'); } ]; for (var i =0, len = methods.length; i < len; i++) { try { methods[i](); } catch(e) { continue; } //If we reach this point, method[i] worked. this.createXhrObject = methods[i]; //Memoize the method. return methods[i]; } // If we reach this point, none of the methods worked. throw new Error('SimpleHandler: Could not create an XHR object.'); } }
主要好处在于消除对象间的耦合,经过使用工厂方法而不是 new 关键字及具体类,你能够把全部实例化代码集中在一个位置.能够大大简化更换所用的类或者在运行期间动态选择全部的类的工做.在派生子类时也更灵活.能够先建立一个抽象的超类,而后在子类中建立工厂方法,从而把成员对象的实例化推迟到更专门化的子类中进行.
全部这些好处都和面向对象设计的两条原则相关: 弱化对象间的耦合:防止代码的重复.在一个方法中进行类的实例化,能够消除重复性的代码.这是在用一个对接口的调用取代一个具体的实现.这些都有助于模块化代码.
不能把工厂方法当万金油,而把普通函数扔在以便.若是根本不可能另外换用一个类或者不须要在运行期间在一系列类的选择,那么就不该该使用工厂方法.大多数最好使用 new 关键字和构造函数公开进行实例化,这样代码会更简单易读..一眼就看到调用的构造函数,没必要去查看某个工厂方法去知道实例化的是什么类.