方法链式调用的实现
写的更少,做的更多。是JQuery的核心理念。
那么链式方法的设计与这个核心理念不谋而合。那么从深层次考虑这种设计其实就是一种Internal DSL。
DSL是指Domain Specific Language,也就是用于描述和解决特定领域问题的语言。
看一段链式代码:
$('input[type="button"]') .eq(0).click(function() { alert('点击我!'); }).end().eq(1) .click(function() { $('input[type="button"]:eq(0)').trigger('click'); }).end().eq(2) .toggle(function() { $('.aa').hide('slow'); }, function() { $('.aa').show('slow'); });
看这个代码的结构:
1.找出type类型为button的input元素
2.找到第一个按钮,并绑定click事件处理函数
3.返回所有按钮,再找到第二个
4.为第二个按钮绑定click事件处理函数
5.为第三个按钮绑定toggle事件处理函数
让代码更贴近作者的思维模式;阅读代码时,让读者更容易理解代码的含义;应用DSL可以有效的提高系统的可维护性
(缩小了实现模型和领域模型的距离,提高了实现的可读性)和灵活性,并且提供开发的效率。
jQuery的这种管道风格的DSL链式代码,总的来说:
1. 节约JS代码;
2.所返回的都是同一个对象,可以提高代码的效率。
通过简单扩展原型方法并通过return this
的形式来实现跨浏览器的链式调用。
利用JS下的简单工厂方法模式,来将所有对于同一个DOM对象的操作指定同一个实例。
这个原理就超简单了,如下代码:
aQuery().init().name()
分解:
a = aQuery(); a.init() a.name()
把代码分解一下,很明显实现链式的基本条件就是要实例对象先创建好,调用自己的方法。
aQuery.prototype = { init: function() { return this; }, name: function() { return this } }
所以我们如果需要链式的处理,只需要在方法内部方法当前的这个实例对象this就可以了,因为返回当前实例的this,从而又可以访问自己的原型了,
这样的就节省代码量,提高代码的效率,代码看起来更优雅。
但是这种方法有一个问题是:所有对象的方法返回的都是对象本身,也就是说没有返回值,所以这种方法不一定在任何环境下都适合。
虽然Javascript是无阻塞语言,但是他并不是没阻塞,而是不能阻塞,
所以他需要通过事件来驱动,异步来完成一些本需要阻塞进程的操作,这样处理只是同步链式,
除了同步链式还有异步链式,异步链式jQuery从1.5开始就引入了Promise.
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script> <title></title> </head> <body> 链式调用$$().setName('孙丽媛-Aaron').getName()的结果: <div id="aaron"></div> <script type="text/javascript"> var $$ = ajQuery = function(selector) { return new ajQuery.fn.init(selector); } ajQuery.fn = ajQuery.prototype = { name: 'aaron', init: function(selector) { this.selector = selector; return this; }, constructor: ajQuery } ajQuery.fn.init.prototype = ajQuery.fn ajQuery.fn.setName = function(myName) { this.myName = myName return this; } ajQuery.fn.getName = function() { $("#aaron").html(this.myName) return this; } $$().setName('孙丽媛-Aaron').getName(); </script> </body> </html>
插件接口的设计
基于插件接口设计的好处也是颇多的,其中一个最重要的好处是把扩展的功能从主体框架中剥离出去,降低了框架的复杂度。
接口的设计好比电脑上的配件如:CPU、内存、硬盘都是作为独立的模块分离出去了,但是主板提供模块的接口,
例如支持串口的硬盘,我只要这个硬盘的接口能插上,甭管是500G还是1000G的容量的硬盘,都能使用。
所以在软件设计中插件接口的提供把独立的功能与框架以一种很宽松的方式松耦合。
jQuery插件的开发分为两种:
1.一种是挂在jQuery命名空间下的全局函数,也可称为静态方法;
2.另一种是jQuery对象级别的方法,即挂在jQuery原型下的方法,这样通过选择器获取的jQuery对象实例也能共享该方法。
提供的接口:
$.extend(target, [object1], [objectN])
接口的使用:
jQuery.extend({ data:function(){}, removeData:function(){} }) jQuery.fn.extend({ data:function(){}, removeData:function(){} })
jQuery的主体框架就是之前提到的那样,通过工厂模式返回一个内部的init构造器生成的对象。
但是根据一般设计者的习惯,如果要为jQuery添加静态方法或者实例方法从封装的角度讲是应该提供一个统一的接口才符合设计的。
jQuery支持自己扩展属性,这个对外提供了一个接口,jQuery.fn.extend()来对对象增加方法,
从jQuery的源码中可以看到,jQuery.extend和jQuery.fn.extend其实是同指向同一方法的不同引用。
这里有一个设计的重点,通过调用的上下文,我们来确定这个方法是作为静态还是实例处理,
在javascript的世界中一共有四种上下文调用方式:
1.方法调用模式、
2.函数调用模式、
3.构造器调用模式、
4.apply调用模式:
jQuery.extend调用的时候上下文指向的是jQuery构造器
jQuery.fn.extend调用的时候上下文指向的是jQuery构造器的实例对象了
通过extend()函数可以方便快速的扩展功能,不会破坏jQuery的原型结构,
jQuery.extend = jQuery.fn.extend = function(){...}; 这个是连等,也就是2个指向同一个函数,怎么会实现不同的功能呢?这就是this力量了!
fn与jQuery其实是2个不同的对象
jQuery.extend 调用的时候,this是指向jQuery对象(jQuery是函数,也是对象!),所以这里扩展在jQuery上。
而jQuery.fn.extend 调用的时候,this指向fn对象,jQuery.fn 和jQuery.prototype指向同一对象,扩展fn就是扩展jQuery.prototype原型对象。
这里增加的是原型方法,也就是对象方法了。所以jQuery的API中提供了以上2个扩展函数
jQuery的extend代码实现比较长,我们简单说一下重点:
aAron.extend = aAron.fn.extend = function() { var options, src, copy, target = arguments[0] || {}, i = 1, length = arguments.length; //只有一个参数,就是对jQuery自身的扩展处理 //extend,fn.extend if (i === length) { target = this; //调用的上下文对象jQuery/或者实例 i--; } for (; i < length; i++) { //从i开始取参数,不为空开始遍历 if ((options = arguments[i]) != null) { for (name in options) { copy = options[name]; //覆盖拷贝 target[name] = copy; } } } return target; }
因为extend的核心功能就是通过扩展收集功能(类似于mix混入),所以就会存在收集对象(target)与被收集的数据,
因为jQuery.extend并没有明确实参,而且是通过arguments来判断的,所以这样处理起来很灵活。
arguments通过判断传递参数的数量可以实现函数重载。
其中最重要的一段target = this
,通过调用的方式我们就能确实当前的this的指向,所以这时候就能确定target了。
最后就很简单了,通过for循环遍历把数据附加到这个target上了。
当然在这个附加的过程中我们还可以做数据过滤、深拷贝等一系列的操作了。
<div id="aaron"></div> <script type="text/javascript"> var $$ = ajQuery = function(selector) { return new ajQuery.fn.init(selector); } ajQuery.fn = ajQuery.prototype = { name: 'aaron', init: function(selector) { this.selector = selector; return this; }, constructor: ajQuery } ajQuery.fn.init.prototype = ajQuery.fn ajQuery.extend = ajQuery.fn.extend = function() { var options, src, copy, target = arguments[0] || {}, i = 1, length = arguments.length; //只有一个参数,就是对jQuery自身的扩展处理 //extend,fn.extend if (i === length) { target = this; //调用的上下文对象jQuery/或者实例 i--; } for (; i < length; i++) { //从i开始取参数,不为空开始遍历 if ((options = arguments[i]) != null) { for (name in options) { copy = options[name]; //覆盖拷贝 target[name] = copy; } } } return target; } ajQuery.fn.extend({ setName: function(myName) { this.myName = myName return this; }, getName: function() { $("#aaron").html(this.myName) return this; } }) $$().setName('孙丽媛-Aaron').getName();