微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

javascript 伪数组实现方法

2015-07-22 20:05 前端大全

(点击上方,可快速关注)


作者:两仪的博客

网址:http://segmentfault.com/a/1190000002998114

点击“阅读原文”可查看本文网页版


什么是伪数组


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


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


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


来看个示例:


var fakeArray01 = {0:'a',1:'b',length:2};//这是一个标准的伪数组对象

var arr01 = Array.prototype.slice.call(fakeArray01);

alert(arr01[0]);//a

var arr02 = [].slice.call(fakeArray01);

alert(arr02[0]);//a


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


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


伪数组的实现


我们来看一些特殊的用例


var fakeArray01 = {a:'a',b:'b',length:2};//没有length下标对应的值

var arr01 = Array.prototype.slice.call(fakeArray01);

alert(arr01[0]);//undefined

var fakeArray02 = {0:'a',1:'b',length:'num'};//length不是数值

var arr02 = Array.prototype.slice.call(fakeArray02);

alert(arr02[1]);//undefined


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

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


function slice(start, end) {

var len = ToUint32(this.length), result = [];

for(var i = start; i < end; i++) {

result.push(this[i]);

}

return result;

}


可以看出,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的实际运用:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>fakeArray</title>

<meta http-equiv="content-type" content="text/html; charset=UTF-8">

<script src="jquery-1.4.2.js" type="text/javascript"></script>

<script>

$(document).ready(function(){

var body = $("body");

alert(body.get(0).tagName);

});

</script>

</head>

<body>

<div id="test"></div>

</body>

</html>


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


jQuery.fn = jQuery.prototype = {

init: function (selector, context) {

var match, elem, ret, doc;

// Handle $(""), $(null), or $(undefined)

if (!selector) {

return this;

}

// Handle $(DOMElement)

if (selector.nodeType) {

this.context = this[0] = selector;

this.length = 1;

return this;

}

// The body element only exists once, optimize finding it

if (selector === "body" && !context) {

this.context = document;

this[0] = document.body;

this.selector = "body";

this.length = 1;

return this;

}

//... ...

},

get: function (num) {

return num == null ?

// Return a 'clean' array

this.toArray() :

// Return just the object

(num < 0 ? this.slice(num)[0] : this[num]);

}

}


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


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


//$(document)

init: function( selector, context ) {

var match, elem, ret, doc;

// Handle $(DOMElement) : 处理DOM元素,

if ( selector.nodeType ) {

this.context = this[0] = selector; //给属性0赋予selector值,此时就是document对象

this.length = 1; //创建伪数组,更新下标

return this; //返回Jquery对象

}

//... ...

}


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


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


get: function( num ) {

return num == null ?

//如果没有num,则直接返回DOM数组

this.toArray() :

//如果指定的num,则返回指定下标的元素

//this.slice是jquery的另一个方法,它内部其实还是调用Array.prototype.slice来实现将伪数组转换为真实的数组

( num < 0 ? this.slice(num)[ 0 ] : this[ num ] );

}


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


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


符合类数组对象的两条规则


  • 它们都有一个合法的 length 属性(0 到 2**32 – 1 之间的正整数)。

  • length 属性的值大于它们的最大索引(index)。


参考链接:


  • http://lifesinger.org/blog/2010/05/array-prototype-slice/ (已经失效)

  • http://segmentfault.com/a/1190000002648510



前端大全

微信号:FrontDev

打造东半球最好的 前端技术 微信号

--------------------------------------

商务合作QQ:2302462408

招聘和猎头服务QQ:2302462408

投稿网址:top.jobbole.com

 
前端大全 更多文章 5个典型的JavaScript面试题(上) Limu:JavaScript的那些书 Web开发:我希望得到的编程学习路线图 JavaScript基础工具清单 常用排序算法之JavaScript实现
猜您喜欢 大数据在营销和销售中的十大应用 七年iOS工作经验的我为什么放弃了iOS而选择了Android 竞品技术三瞥 安装包的大小 面向开发者服务的用户体验 终于,你还是变了!360加固保崩溃日志分析服务全新上线!