经过new操做符构建一个对象,通常通过四步:javascript
A.建立一个新对象css
B.将构造函数的做用域赋给新对象(因此this就指向了这个新对象)html
C.执行构造函数中的代码java
D.返回这个新对象jquery
最后一点就说明了,咱们只要返回一个新对象便可。其实new操做符主要是把原型链跟实例的this关联起来,这才是最关键的一点,因此咱们若是须要原型链就必需要new操做符来进行处理。不然this则变成window对象了。算法
改造jQuery无new的格式,咱们能够经过instanceof判断this是否为当前实例:设计模式
var $$ = ajQuery = function(selector) { if(!(this instanceof ajQuery)){ // 第二次看仍是以为这一句很NB return new ajQuery(selector); } this.selector = selector; return this }
但在jQuery实际上采起的手段是把原型上的一个init方法做为构造器,这样貌似更节省代码空间?数组
var $$ = ajQuery = function(selector) { //把原型上的init做为构造器 return new ajQuery.fn.init( selector ); } ajQuery.fn = ajQuery.prototype = { name: 'aaron', init: function() { console.log(this) }, constructor: ajQuery }
但这样子还缺点东西,init是ajQuery原型上做为构造器的一个方法,那么其this就不是ajQuery了,因此this就彻底引用不到ajQuery的原型了,因此这里经过new把init方法与ajQuery给分离成2个独立的构造器。app
接着上面分割出2个构造器的疑问,来看看jQuery的一个遍历接口:框架
$(".aaron").each() //做为实例方法存在 $.each() //做为静态方法存在
看似实例和静态方法须要两个函数来实现,但在jQuery源码中是这样的:
jQuery.prototype = { // 调用实例方法其实是将实例对象this做为一个参数,调用对应的静态方法,这样就造成了共享 each: function( callback, args ) { return jQuery.each( this, callback, args ); } }
实例方法取于静态方法,换句话来讲这是静态与实例方法共享设计,静态方法挂在jQuery构造器上,原型方法挂在哪里呢?------jQuery经过new原型prototype上的init方法看成构造器,那么init的原型链方法就是实例的方法了,因此jQuery经过2个构造器划分2种不一样的调用方式一种是静态,一种是原型。
那若是要将2个构造器原型关联起来,关键就是靠下面一句:
ajQuery.fn.init.prototype = ajQuery.fn
这样init构造出来的实例对象也可以继承jQuery原型上的方法了。
jQuery的核心理念是Write less,Do more(写的更少,作的更多),那么链式方法的设计与这个核心理念不谋而合。那么从深层次考虑这种设计其实就是一种Internal DSL。
DSL是指Domain Specific Language,也就是用于描述和解决特定领域问题的语言。
jQuery的Internal DSL形式带来的好处——编写代码时,让代码更贴近做者的思惟模式;阅读代码时,让读者更容易理解代码的含义;应用DSL能够有效的提升系统的可维护性(缩小了实现模型和领域模型的距离,提升了实现的可读性)和灵活性,而且提供开发的效率。
jQuery的这种管道风格的DSL链式代码,总的来讲:
☑ 节约JS代码;
☑ 所返回的都是同一个对象,能够提升代码的效率
实现链式操做的原理你们都懂的,就只须要在方法内返回当前的这个实例对象this就能够了,由于返回当前实例的this,从而又能够访问本身的原型了,这样的就节省代码量,提升代码的效率,代码看起来更优雅。可是这种方法有一个问题是:全部对象的方法返回的都是对象自己,也就是说没有返回值,因此这种方法不必定在任何环境下都适合。
jQuery插件的开发分为两种:
☑ 一种是挂在jQuery命名空间下的全局函数,也可称为静态方法;
☑ 另外一种是jQuery对象级别的方法,即挂在jQuery原型下的方法,这样经过选择器获取的jQuery对象实例也能共享该方法。
提供的接口:
$.extend(target, [object1], [objectN]); $.fn.extend();
接口的使用:
// 拓展到jQuery上的静态方法 jQuery.extend({ data:function(){}, removeData:function(){} }) // 拓展到实例对象上的原型方法 jQuery.fn.extend({ data:function(){}, removeData:function(){} })
而jQuery源码中对于上面两种扩展,实际上是同指向同一方法的不一样引用(这里有一个设计的重点,经过调用的上下文,咱们来肯定这个方法是做为静态仍是实例处理,在javascript的世界中一共有四种上下文调用方式:方法调用模式、函数调用模式、构造器调用模式、apply调用模式),而这一切都是依靠this来完成的。
☑ jQuery.extend调用的时候上下文指向的是jQuery构造器,this指向的是jQuery ☑ jQuery.fn.extend调用的时候上下文指向的是jQuery构造器的实例对象了,this指向实例对象
所以在源码中是这样的:
aAron.extend = aAron.fn.extend = function() { var options, src, copy, target = arguments[0] || {}, i = 1, length = arguments.length; // 只有一个参数,就是对jQuery自身的扩展处理 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上了。固然在这个附加的过程当中咱们还能够作数据过滤、深拷贝等一系列的操做了。
经过jQuery处理后返回的不只仅只有DOM对象,而是一个包装容器,返回jQuery对象。而这一个对象中有一个preObject的属性。
要了解这个属性是作什么的,首先了解一下jQuery对象栈,jQuery内部维护着一个jQuery对象栈。每一个遍历方法(在当前选中范围内的DOM再进行筛选的操做,例如.find()方法)都会找到一组新元素(一个jQuery对象),而后jQuery会把这组元素推入到栈中。
而每一个jQuery对象都有三个属性:context、selector和prevObject(用id选择器的话这个属性不必定有),其中的prevObject属性就指向这个对象栈中的前一个对象,而经过这个属性能够回溯到最初的DOM元素集中。
能够看下下面的例子:
$("div").find('.foo').find('.aaa') // 这里的preObject属性就会指向$("div").find('.foo')的DOM集合 $("div").find('.foo') // 往前一级的preObect属性就是指向$("div")的DOM集合
而这种能够回溯到以前选择的DOM集合的机制,是为这两个方法服务的:
.end() // 回溯到前一个jQuery对象,即prevObject属性 .addBack() // 把当前位置和前一个位置的元素结合组合起来,而且将这个新的组合的元素集推入栈的上方
而利用这个回溯机制和对应的方法能够进行以下的操做:
<ul class="first"> <li class="foo">list item 1</li> <li>list item 2</li> <li class="bar">list item 3</li> </ul> <script> // foo类li标签背景设置为红色, bar类li标签背景设置为绿色 $("#test2").click(function(){ //经过end连贯处理 $('ul.first') .find('.foo') .css('background-color', 'red') .end() .find('.bar') .css('background-color', 'green'); }) </scripts>
利用这个DOM元素栈能够减小重复的查询和遍历的操做,而减小重复操做也正是优化jQuery代码性能的关键所在。
end方法可以帮助咱们回溯到上一个DOM合集,所以该方法返回的就是一个jQuery对象,在源码中的表现就是返回了prevObject对象:
end: function() { return this.prevObject || this.constructor(null); }
那么prevObject在什么状况下会产生?
在构建jQuery对象的时候,经过pushStack方法构建,以下代码:
pushStack: function( elems ) { // Build a new jQuery matched element set // 这里将传进来的DOM元素,经过调用jQuery的方法构建成一个新的jQuery对象 var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) // 在此对象上把前一个jQuery对象添加到prevObject属性中 ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set // 最后返回这个jQuery对象 return ret; }
那么在find方法中,为了将前一个jQuery对象推入栈中,就会调用这个pushStack方法来构建:
jQuery.fn.extend({ find: function(selector) { //...........................省略................................ //经过sizzle选择器,返回结果集 jQuery.find(selector, self[i], ret); // Needed because $( selector, context ) becomes $( context ).find( selector ) ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret); // 这里就是将实例对象推入栈中,而后返回新的jQuery对象 ret.selector = this.selector ? this.selector + " " + selector : selector; return ret; } }
jQuery既然是模仿的数组结构,那么确定会实现一套类数组的处理方法,好比常见的栈与队列操做push、pop、shift、unshift、求和、遍历循环each、排序及筛选等一系的扩展方法。
jQuery提供了.get()、:index()、 :lt()、:gt()、:even()及 :odd()这类索引值相关的选择器,他们的做用能够过滤他们前面的匹配表达式的集合元素,筛选的依据就是这个元素在原先匹配集合中的顺序。
先来看看get方法的实现源码:
get: function(num) { return num != null ? // 不传参为undefined, 走false线 // Return just the one element from the set (num < 0 ? this[num + this.length] : this[num]) : // Return all the elements in a clean array slice.call(this); // 返回整个DOM元素数组 }
熟悉jQuery的童鞋都清楚,get返回的是DOM元素,而eq返回的是jQuery对象,这样就能够继续执行链式操做。
eq实现的原理:
eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
上面实现代码的逻辑就是跟get是同样的,区别就是经过了pushStack产生了一个新的jQuery对象。
若是须要的是一个合集对象要怎么处理?所以jQuery便提供了一个slice方法,根据下标范围取元素集合,并生成一个新的jQuery对象。
slice方法实现源码:
slice: function() { return this.pushStack( slice.apply( this, arguments ) ); },
迭代器是一个框架的重要设计。咱们常常须要提供一种方法顺序用来处理聚合对象中各个元素,而又不暴露该对象的内部,这也是设计模式中的迭代器模式(Iterator)。
针对迭代器,这里有几个特色:
☑ 访问一个聚合对象的内容而无需暴露它的内部。
☑ 为遍历不一样的集合结构提供一个统一的接口,从而支持一样的算法在不一样的集合结构上进行操做。
☑ 遍历的同时更改迭代器所在的集合结构可能会致使问题。
另外还要考虑这四点:
☑ 聚合对象,多是对象,字符串或者数组等类型
☑ 支持参数传递
☑ 支持上下文的传递
☑ 支持循环中退出(返回false的时候退出循环,节省性能)
简单实现一个迭代器:
function each(obj, callback, context, arg) { var i = 0; var value; var length = obj.length; for (; i < length; i++) { value = callback.call(context || null, obj[i], arg); if (value === false) { break; } }
$.each()函数和$(selector).each()是不同的,后者是专门用来遍历一个jQuery对象的,是为jQuery内部服务的。
jQuery的实例方法最终也是调用的静态方法,咱们在以前就解释过jQuery的实例与原型方法共享的设计。
$.each()实例方法以下:
// 内部是直接调用的静态方法 each: function(callback, args) { return jQuery.each(this, callback, args); },
jQuery.each静态方法:
each: function(obj, callback, args) { var value, i = 0, length = obj.length, isArray = isArraylike(obj); if (args) { if (isArray) { for (; i < length; i++) { value = callback.apply(obj[i], args); if (value === false) { break; } } } else { for (i in obj) { value = callback.apply(obj[i], args); if (value === false) { break; } } }
实现原理几乎一致,只是增长了对于参数的判断。对象用for in遍历,数组用for遍历。