1. What is chain call
This is easy to understand, for example:
$(this).setStyle('color', 'red').show();
The difference between general function calls and chain calls: after calling a method, return this returns the object of the current calling method.
function Dog(){ this.run= function(){ alert("The dog is running...."); return this;//返回当前对象 Dog }; this.eat= function(){ alert("After running the dog is eatting...."); return this;//返回当前对象 Dog }; this.sleep= function(){ alert("After eatting the dog is running...."); return this;//返回当前对象 Dog }; } //一般的调用方式; /* var dog1 =new Dog(); dog1.run(); dog1.eat(); dog1.sleep();*/ var dog2 = new Dog(); dog2.run().eat().sleep();
2. Decompose chain calls
Chain call is actually two parts:
1). Operation object (that is, the DOM element being manipulated, such as $(this) in the above example)
2). Operation method (specifically what to do, such as setStyle and show in the above example)
How to implement operation objects and operation methods
Create a generic $function:
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; }
However, if you transform this function into a constructor, save those elements as an array in an instance attribute, and let all methods defined in the object pointed to by the prototype attribute of the constructor function return to call the method A reference to that instance, then it has the ability to chain calls. (Having said so much, just return this; at the end of each method),
I first need to change this $function into a factory method, which is responsible for creating objects that support chain calls. This function should be able to accept arguments in the form of an array of elements so that we can use the same public interface as before. In this way, it has the ability to make chain calls.
The transformation is as follows:
(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; //在每个方法的最后return this; }, setStyle: function(prop, val){ this.each(function(el){ el.style[prop] = val; }); return this; //在每个方法的最后return this; }, show: function(){ var that = this; this.each(function(el){ that.setStyle('display', 'block'); }); return this; //在每个方法的最后return this; }, addEvent: function(type, fn){ var add = function(el){ if(window.addEventListener){ el.addEventListener(type, fn, false); }else if(window.attachEvent){ el.addEvent('on'+type, fn); } }; this.each(function(el){ add(el); }); return this; //在每个方法的最后return this; } } window.$ = function(){ return new _$(arguments); } })();
Return this at the end, which passes the object of the calling method to the next method in the calling chain.
3. Simulate jquery underlying chain programming
// 块级作用域 //特点1 程序启动的时候 里面的代码直接执行了 //特点2 内部的成员变量 外部无法去访问 (除了不加var修饰的变量) (function(window , undefined){ // $ 最常用的对象 返回给外界 大型程序开发 一般使用'_'作为私用的对象(规范) function _$(arguments){ //实现代码...这里仅实现ID选择器 // 正则表达式匹配id选择器 var idselector = /#\w+/ ; this.dom ; // 此属性 接受所得到的元素 // 如果匹配成功 则接受dom元素 arguments[0] = '#inp' if(idselector.test(arguments[0])){ this.dom = document.getElementById(arguments[0].substring(1)); } else { throw new Error(' arguments is error !'); } }; // 在Function类上扩展一个可以实现链式编程的方法 Function.prototype.method = function(methodName , fn){ this.prototype[methodName] = fn ; return this ; //链式编程的关键 } // 在_$的原型对象上 加一些公共的方法 _$.prototype = { constructor : _$ , addEvent:function(type,fn){ // 给你的得到的元素 注册事件 if(window.addEventListener){// FF this.dom.addEventListener(type , fn); } else if (window.attachEvent){// IE this.dom.attachEvent('on'+type , fn); } return this ; }, setStyle:function(prop , val){ this.dom.style[prop] = val ; return this ; } }; // window 上先注册一个全局变量 与外界产生关系 window.$ = _$ ; // 写一个准备的方法 _$.onReady = function(fn){ // 1 实例化出来_$对象 真正的注册到window上 window.$ = function(){ return new _$(arguments); }; // 2 执行传入进来的代码 fn(); // 3 实现链式编程 _$.method('addEvent',function(){ // nothing to do }).method('setStyle',function(){ // nothing to do }); }; })(window); // 程序的入口 window传入作用域中 $.onReady(function(){ var inp = $('#inp'); //alert(inp.dom.nodeName); //alert($('#inp')); inp.addEvent('click',function(){ alert('我被点击了!'); }).setStyle('backgroundColor' , 'red'); });
4. Use callback functions to obtain data from methods that support chain calls
Chained calls are very suitable for assigner methods, but for value getter methods, it is inconvenient because each method returns this.
However, there is still a workaround, and that is the callback function.
When callback function is not used
//without callback window.API = window.API || function(){ var name = 'JChen'; this.setName = function(newName){ name = newName; return this; }; this.getName = function(){ return name; }; }; var o = new API(); console.log(o.getName()); console.log(o.setName('Haha').getName());
When using callback function
//with callback window.API2 = window.API2 || function(){ var name = 'JChen'; this.setName = function(newName){ name = newName; return this; }; this.getName = function(callback){ callback.call(this, name); return this; }; }; var o2 = new API2(); o2.getName(console.log).setName('Hehe').getName(console.log);
When using callback functions, callback.call(this, name) is generally fine. However, in this example, console.log is used, so there is a problem. The reason is that this of the console points to the console rather than winodw.
This problem is also easy to solve. As follows:
//with callback window.API2 = window.API2 || function(){ var name = 'JChen'; this.setName = function(newName){ name = newName; return this; }; this.getName = function(callback){ callback.call(this, name); return this; }; }; var o2 = new API2(); var log = function(para){ console.log(para); }; o2.getName(log).setName('Hehe').getName(log);
This style of chain calling helps simplify code writing, making the code more concise and easier to read, and also avoids reusing an object variable multiple times. I hope everyone can master it.