这篇文章来回答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}; |
3 |
var arr01 = Array.prototype.slice.call(fakeArray01); |
6 |
var arr02 = [].slice.call(fakeArray01); |
slice 可以用来获取数组片段,它返回新数组,不会修改原数组。
示例中可以看到fakeArray被成功的转换成了Array对象。也许大家对Array.prototype.slice.call这种写法比较陌生,其实我们也可以通过[].slice.call这种形式实现同样的效果,那为什么我们要通过prototype的形式实现呢,答案是以prototype的形式执行程序效率更高,同样代码也更加优美。
伪数组的实现
让我们再深入的看一下伪数组的实现。
我们来看一些特殊的用例:
1 |
var fakeArray01 = {a: 'a' ,b: 'b' ,length:2}; |
2 |
var arr01 = Array.prototype.slice.call(fakeArray01); |
5 |
var fakeArray02 = {0: 'a' ,1: 'b' ,length: 'num' }; |
6 |
var arr02 = Array.prototype.slice.call(fakeArray02); |
同样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++) { |
可以看出,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" > |
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> |
08 |
$(document).ready( function (){ |
10 |
alert(body.get(0).tagName); |
再简单不过的程序了,好,让我们来看一下其内部的实现原理:
01 |
jQuery.fn = jQuery.prototype = { |
02 |
init: function ( selector, context ) { |
03 |
var match, elem, ret, doc; |
11 |
if ( selector.nodeType ) { |
12 |
this .context = this [0] = selector; |
18 |
if ( selector === "body" && !context ) { |
19 |
this .context = document; |
20 |
this [0] = document.body; |
21 |
this .selector = "body" ; |
28 |
get: function ( num ) { |
33 |
( num < 0 ? this .slice(num)[ 0 ] : this [ num ] ); |
最后,我们来解释一下,程序的执行细节.但是在这之前,还得说一下关于Jquery的内部的一些东西。
用过Jquery的用户应该都知道()函数,它是Jquery的选择器代表。我们可能通过()函数去选取页面中的元素(具体语法可参数Jquery帮助文档)。实际上当我们执行()函数时,程序去执行上面列出的init方法,我们来看一下在调用(document)时所发生的事件:
02 |
init: function ( selector, context ) { |
03 |
var match, elem, ret, doc; |
05 |
if ( selector.nodeType ) { |
06 |
this .context = this [0] = selector; |
$("body")是同样的道理,不再多说了。
我们知道Jquery里所有的操作返回的都是Jquery对象,那我们如何得到其所对应的dom对象呢,Jquery为我们提供了一个get方法,这是专门用来从jquery对象中取得DOM对象用的,由此,便有了body.get(0),那为什么又是get(0)而不是get()呢,因为Jquery的所有操作都是针对于数组进行的。所以,在get方法里,我们要传一个下标值,来得到具体的元素。现在该看get方法的具体实现了:
7 |
( num < 0 ? this .slice(num)[ 0 ] : this [ num ] ); |
关于伪数组就到这吧,我想应该已经差不多了。
注:有机会的话,将来可能会出一个"超越Jquery"系列,专门分析Jquery内部执行细节。但是由于Jquery内部的有各种歪门邪道的手法还不是很理解,所以这是将来的问题了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· [AI/GPT/综述] AI Agent的设计模式综述
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!