zoukankan      html  css  js  c++  java
  • 伪数组

    这篇文章来回答javascript通用循环遍历方法forEach中最后提到的关于伪数组的问题。

    什么是伪数组

    能通过Array.prototype.slice转换为真正的数组的带有length属性的对象。

    这种对象有很多,比较特别的是arguments对象,还有像调用getElementsByTagName,document.childNodes之类的,它们都返回NodeList对象都属于伪数组。

    我们可以通过Array.prototype.slice.call(fakeArray)将伪数组转变为真正的Array对象。

    来看个示例:

    1 var fakeArray01 = {0:'a',1:'b',length:2};//这是一个标准的有伪数组对象
    2          
    3 var arr01 = Array.prototype.slice.call(fakeArray01);
    4 alert(arr01[0]);//a
    5          
    6 var arr02 =  [].slice.call(fakeArray01);
    7 alert(arr02[0]);//a

    slice 可以用来获取数组片段,它返回新数组,不会修改原数组。

    示例中可以看到fakeArray被成功的转换成了Array对象。也许大家对Array.prototype.slice.call这种写法比较陌生,其实我们也可以通过[].slice.call这种形式实现同样的效果,那为什么我们要通过prototype的形式实现呢,答案是以prototype的形式执行程序效率更高,同样代码也更加优美。

     

    伪数组的实现

     让我们再深入的看一下伪数组的实现。

    我们来看一些特殊的用例:

    1 var fakeArray01 = {a:'a',b:'b',length:2};//没有length下标对应的值
    2 var arr01 = Array.prototype.slice.call(fakeArray01);
    3 alert(arr01[0]);//undefined
    4          
    5 var fakeArray02 = {0:'a',1:'b',length:'num'};//length不是数值
    6 var arr02 = Array.prototype.slice.call(fakeArray02);
    7 alert(arr02[1]);//undefined

    同样fakeArray01和fakeArray02被转换成了真正的数组,但是数组中的值都为undefined

    查看 V8 引擎 array.js 的源码,可以将 slice 的内部实现简化为:

     

    1 function slice(start, end) {
    2     var len = ToUint32(this.length), result = [];
    3     for(var i = start; i < end; i++) {
    4         result.push(this[i]);
    5     }
    6     return result;
    7 }

    可以看出,slice 并不需要 this 为 array 类型,只需要有 length 属性即可。并且 length 属性可以不为 number 类型,当不能转换为数值时,ToUnit32(this.length) 返回 0.

    根据以上结论可以得出:fakeArray01被转换成了lenth为2的数组,其值都被初始化为undefined,fakeArray02被转换成了length为0的数组,自然访问下标为1的元素返回undefined

     

    IE的问题

     针对于标准浏览器slice实现已经可以解释所有的问题,但是IE在处理NodeList时出现了问题。IE中无法将NodeList转换为真正的数组,会出错。这又是为什么呢?严格说,在IE内部定义了一个抽象类Arraioid,Array和Arguments都继承与此,所以可以用slice。但DOM对象是通过COM接入到JScript的,slice检测的时候失效。

     

    Jquery与伪数组

    Jquery内部大量运用了伪数组。可以说整个Jquery对象,都是构建在伪数组的基础之上的,好让我们来看一些Jquery的实际运用:

    01 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    02 <html>
    03   <head>
    04     <title>fakeArray</title>
    05     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    06     <script src="jquery-1.4.2.js" type="text/javascript"></script>
    07     <script>
    08     $(document).ready(function(){
    09         var body = $("body");
    10         alert(body.get(0).tagName);
    11     });
    12     </script>
    13   </head>
    14   <body>
    15      <div id="test"></div>
    16   </body>
    17 </html>

    再简单不过的程序了,好,让我们来看一下其内部的实现原理:

    01 jQuery.fn = jQuery.prototype = {
    02     init: function( selector, context ) {
    03         var match, elem, ret, doc;
    04  
    05         // Handle $(""), $(null), or $(undefined)
    06         if ( !selector ) {
    07             return this;
    08         }
    09  
    10         // Handle $(DOMElement)
    11         if ( selector.nodeType ) {
    12             this.context = this[0] = selector;
    13             this.length = 1;
    14             return this;
    15         }
    16          
    17         // The body element only exists once, optimize finding it
    18         if ( selector === "body" && !context ) {
    19             this.context = document;
    20             this[0] = document.body;
    21             this.selector = "body";
    22             this.length = 1;
    23             return this;
    24         }
    25          
    26         //... ...
    27     },
    28     get: function( num ) {
    29         return num == null ?
    30             // Return a 'clean' array
    31             this.toArray() :
    32             // Return just the object
    33             ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] );
    34     }
    35 }

    最后,我们来解释一下,程序的执行细节.但是在这之前,还得说一下关于Jquery的内部的一些东西。

    用过Jquery的用户应该都知道$()函数,它是Jquery的选择器代表。我们可能通过$()函数去选取页面中的元素(具体语法可参数Jquery帮助文档)。实际上当我们执行$()函数时,程序去执行上面列出的init方法,我们来看一下在调用$(document)时所发生的事件:

    01 //$(document)
    02 init: function( selector, context ) {
    03     var match, elem, ret, doc;
    04     // Handle $(DOMElement) : 处理DOM元素,
    05     if ( selector.nodeType ) {
    06         this.context = this[0] = selector; //给属性0赋予selector值,此时就是document对象
    07         this.length = 1; //创建伪数组,更新下标
    08         return this//返回Jquery对象
    09     }
    10     //... ...
    11 }

    $("body")是同样的道理,不再多说了。

    我们知道Jquery里所有的操作返回的都是Jquery对象,那我们如何得到其所对应的dom对象呢,Jquery为我们提供了一个get方法,这是专门用来从jquery对象中取得DOM对象用的,由此,便有了body.get(0),那为什么又是get(0)而不是get()呢,因为Jquery的所有操作都是针对于数组进行的。所以,在get方法里,我们要传一个下标值,来得到具体的元素。现在该看get方法的具体实现了:

    1 get: function( num ) {
    2     return num == null ?
    3         //如果没有num,则直接返回DOM数组
    4         this.toArray() :
    5         //如果指定的num,则返回指定下标的元素
    6         //this.slice是jquery的另一个方法,它内部其实还是调用Array.prototype.slice来实现将伪数组转换为真实的数组
    7         ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] );
    8 }

     

    关于伪数组就到这吧,我想应该已经差不多了。

     

    注:有机会的话,将来可能会出一个"超越Jquery"系列,专门分析Jquery内部执行细节。但是由于Jquery内部的有各种歪门邪道的手法还不是很理解,所以这是将来的问题了。

  • 相关阅读:
    《一线架构师》之Refined Architecture阶段
    可修改性战术分析
    上周学习总结
    软件质量属性之可修改性
    《信息领域热词分析》实现六种质量属性
    质量属性的六个常见属性应用场景(淘宝篇)
    软件架构师如何工作
    信息领域热词分析
    结合ssh谈MVC架构模式
    PythonCPPJava的异同
  • 原文地址:https://www.cnblogs.com/aaronjs/p/2167541.html
Copyright © 2011-2022 走看看