β

zepto.js之旅(番外):深入了解jQuery的类数组对象

F2ES.COMF2ES.COM 2698 阅读

Zepto生成节点数组的方法

之前在《zepto.js之旅(一)》中提到了zepto实现继承的方式,通过__proto__属性来给zepto生成的对象增加各种方法。而这个时候,很多人就会有疑问,如果我传入的对象是一个window,那么直接修改window的__proto__属性会不会有什么问题,或者说我传入的是一个由querySelectorAll生成的节点数组。比如下面的例子:

var div = $$('div'),//$$是document.querySelectorAll的简写
 method = {
 get : function(){
 //do something
 }
 }
console.log(div.__proto__); 
div.__proto__ = $.fn;

一个简单的运行结果:

vnoCW zepto.js之旅(番外):深入了解jQuery的类数组对象

很明显querySelectorAll生成的节点列表本身就含有一个__proto__属性,包含了item方法,那么直接修改节点数组的__proto__肯定不是一个合适的方案。

所以,zepto在拿到节点数组的时候,会用slice做一次处理

zepto.qsa = function(element, selector){
  var found
    return (element === document && idSelectorRE.test(selector)) ?
      ( (found = element.getElementById(RegExp.$1)) ? [found] : emptyArray ) :
      (element.nodeType !== 1 && element.nodeType !== 9) ? emptyArray :
      slice.call( //slice后生成一个纯粹的数组对象
        classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) :
        tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) :
        element.querySelectorAll(selector)
 )
}

那么,这个时候,还有人会有疑问,转换成纯粹的数组对象后再修改__proto__就没问题了么?

还是看例子:

[].slice.call($$('div')).__proto__.push
//输出function push() { [native code] }

因为纯粹的数组对象的__proto__指向的是Array.prototype,如果对其进行赋值,将会造成这个纯粹的数组对象无法使用Array.prototype上的方法。这时,zepto的解决方案就是把这些本来属于Array.prototype上的方法的一部分放到$.fn里,这样就可以就可以在zepto生成的节点数组中使用到这些方法。

在zepto源码里具体就是这么一个过程:

dom.__proto__ = arguments.callee.prototype //arguments.callee指向zepto.Z
zepto.Z.prototype = $.fn
$.fn = {
  // Because a collection acts like an array
  // copy over these useful array functions.
  forEach: emptyArray.forEach,
  reduce: emptyArray.reduce,
  push: emptyArray.push,
  indexOf: emptyArray.indexOf,
  concat: emptyArray.concat
}

好吧,这里说了很多zepto生成节点数组的方式,下面主要讲述一下jQuery在这块的处理。

 jQuery生成类节点数组对象的方式

这里为什么要用“类节点数组”来形容jQuery生成的节点对象呢?

我们在上面的zepto相关介绍中可以发现,zepto生成的节点数组是真正的数组,也就是经得起instance考验的。而jQuery在这一块的处理就截然不同了。

不同的原因

jQuery比起zepto有着更广泛的支持平台,面临的问题也比zepto复杂很多。最主要的一个问题就是如果通过$()方法生成一个节点数组,而且这个节点数组又能直接$(selector).xxx_method()。

zepto的平台优势可以让它直接通过修改__proto__来引用这些方法,但是IE很不幸拒绝了开发者对__proto__的访问。

ig4c4 zepto.js之旅(番外):深入了解jQuery的类数组对象

这也就意味着jQuery只能通过new一个实例来连接生成的节点对象和jQuery.fn上的方法。

首先,我们需要明确jQuery在这个地方所需要做的,当我们调用$(selector):

  1. 返回一个节点数组
  2. 让节点数组继承jQuery.fn上的方法

OK,这时矛盾就出现了。看一个例子:

function a(){
  return [1,2]
}
a.prototype.getName = function(){}
var b = new a;
b.getName();
//TypeError: Object 1,2 has no method 'getName'

这样的方案,我们成功地返回了一个数组,但是这样的话,数组就会丢失原型上的方法,因为这些方法只对实例有效
再看另外一种方式

function a(){}
a.prototype.getName = function(){ return 'access' }
var b = new a;
b.getName();
//"access"

比较两个例子,我们就会发现,如果想继承jQuery.fn上的方法,$(selector)只能返回实例,也就是一个对象字面量,不可能返回一个纯粹的数组。
于是,jQuery增加了一堆处理,让这个对象变得像一个数组。
在这里,肯定又会有人有疑问,‘控制台里输出一个jQuery的实例,显示的不就是数组么?”
这个问题,我也是在stackoverflow上无意间看到的解释。
大家可以点击上面的连接看下这位同学的小发现,我在这边也简单说明下,在满足下面条件的时候,控制台就会把一个对象作为数组形式输出

所以我们在控制台看到的jQuery实例并不是数组,是一个对象,具体大家如果没兴趣看源码的话,可以直接$.type或者instanceof一下。

如何生成类数组对象

我们已经知道类数组对象的所需要的条件,那么,接下来就是如何生成一个类数组对象的问题

这之中最重要的就是jQuery的makeArray方法。

makeArray: function( arr, results ) {
  var type,
   ret = results || [];
  if ( arr != null ) {
    // The window, strings (and functions) also have 'length'
    // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
    type = jQuery.type( arr );
    if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
      core_push.call( ret, arr );//将其push进去
    } else {
      jQuery.merge( ret, arr );
    }
  }
  return ret;
}

整个过程其实就将节点按顺序赋到jQuery实例上。

这里有人又会有疑问,为什么可以对一个对象字面量进行push操作?

ES5的15.4.4节内置对象Array的介绍中,有一些特殊的说明,比如Array.prototype.concat里有这么一句话:

NOTE The concat function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the concat function can be applied successfully to a host object is implementation-dependent.

简单地说,就是Array.prototype上的部分方法并不仅仅在Array的实例中使用,也可以在其他类型中试用,具体是依赖浏览器实现的。

其他类库的类数组对象生成方式可以参考:将”类数组对象”转换成数组对象

 参考资料

作者:F2ES.COMF2ES.COM
保持学习的心态

发表评论