起因

写这篇博客的起因,是我在知乎上回答一个问题时,说自己在学前端时把《JavaScript高级程序设计》看了好几遍。
于是在评论区中,出现了如下的对话:

天啦噜,这话说的,宝宝感觉到的,是满满的恶意啊。还好自己的JavaScript基础还算不错,没被打脸。(吐槽一句:知乎少部分人真的是恶意度爆表,整天想着打别人的脸。都是搞技术的,和善一点不行吗…………)

不过这个话题也引起了我的注意,问了问身边很多前端同学关于数组与类数组的区别。他们都表示不太熟悉,所以决定写一篇博客,来分享我对数组与类数组的理解。

什么是类数组

类数组的定义,有如下两条:

  • 具有:指向对象元素的数字索引下标以及 length 属性告诉我们对象的元素个数

  • 不具有:诸如 push 、 forEach 以及 indexOf 等数组对象具有的方法Q

这儿有三个典型的JavaScript类数组例子。

1. DOM方法

// 获取所有div
let arrayLike = document.querySelectorAll('div')console.log(Object.prototype.toString.call(arrayLike))  // [object NodeList]console.log(arrayLike.length) // 127console.log(arrayLike[0])
// <div id="js-pjax-loader-bar" class="pjax-loader-bar"></div>console.log(Array.isArray(arrayLike)) // falsearrayLike.push('push')
// Uncaught TypeError: arrayLike.push is not a function(…)

是的,这个arrayLike的 NodeList,有length,也能用数组下标访问,但是使用Array.isArray测试时,却告诉我们它不是数组。直接使用push方法时,当然也会报错。
但是,我们可以借用类数组方法:

let arr = Array.prototype.slice.call(arrayLike, 0)console.log(Array.isArray(arr)) // truearr.push('push something to arr')
console.log(arr[arr.length - 1]) // push something to arr

不难看出,此时的arrayLike在调用数组原型方法时,返回值已经转化成数组了。也能正常使用数组的方法。

2. 类数组对象

let arrayLikeObj = {length: 2,0: 'This is Array Like Object',1: true
}console.log(arrayLikeObj.length) // 2
console.log(arrayLikeObj[0]) // This is Array Like Object
console.log(Array.isArray(arrayLikeObj)) // falselet arrObj = Array.prototype.slice.call(arrayLikeObj, 0)
console.log(Array.isArray(arrObj)) // true

这个例子也很好理解。一个对象,加入了length属性,再用Array的原型方法处理一下,摇身一变成为了真的数组。

3. 类数组函数

这个应该算是最好玩,也是最迷惑人的类数组对象了。

let arrayLikeFunc1 = function () {}
console.log(arrayLikeFunc1.length) // 0
let arrFunc1 = Array.prototype.slice.call(arrayLikeFunc1, 0)
console.log(arrFunc1, arrFunc1.length) // ([], 0)let arrayLikeFunc2 = function (a, b) {}
console.log(arrayLikeFunc2.length) // 2
let arrFunc2 = Array.prototype.slice.call(arrayLikeFunc2, 0)
console.log(arrFunc2, arrFunc2.length) // ([undefined × 2], 2)

可以看出,函数也有length属性,其值等于函数要接收的参数。

注:不适用于ES6的rest参数。具体原因和表现这儿就不再阐述了,不属于本文讨论范围。可参见 《rest参数 - ECMAScript 6 入门》。另外arguments在ES6中,被rest参数代替了,所以这儿不作为例子。

而length属性大于0时,如果转为数组,则数组里的值会是undefined。个数等于函数length的长度。

类数组的实现原理

类数组的实现原理,主要有以下两点:
第一点是JavaScript的“万物皆对象”概念。
第二点则是JavaScript支持的“鸭子类型”。

首先,从第一点开始解释。

万物皆对象

万物皆对象具体解释如下:

在JavaScript中,“一切皆对象”,数组和函数本质上都是对象,就连三种原始类型的值——数值、字符串、布尔值——在一定条件下,也会自动转为对象,也就是原始类型的“包装对象”。

而另外一个要点则是,所有对象都继承于Object。所以都能调用对象的方法,比如使用点和方括号访问属性。
比如说,这样的:

let func = function() {}
console.log(func instanceof Object) // true
func[0] = 'I\'m a func'
console.log(func[0]) // 'I\'m a func'

鸭子类型

万物皆对象具体解释如下:

如果它走起来像鸭子,而且叫起来像鸭子,那么它就是鸭子。

比如说上面举的类数组例子,虽然他们是对象/函数,但是只要有length属性和对应的数字下标,那么他们就是数组。

但是,在这儿,还是有些迷糊的。为什么使用call/apply借用数组方法就能处理这些类数组呢?

探秘V8

一开始,我也对这个犯迷糊啊。直到我去Github上,看到了谷歌V8引擎处理数组的源代码。
地址在这儿:v8/array.js
作为讲述,我们在这里引用push的源代码(方便讲述,删除部分。slice的比较长,但是原理一致):

// Appends the arguments to the end of the array and returns the new
// length of the array. See ECMA-262, section 15.4.4.7.
function ArrayPush() {// 获取要处理的数组var array = TO_OBJECT(this);// 获取数组长度var n = TO_LENGTH(array.length);// 获取函数参数长度var m = arguments.length;for (var i = 0; i < m; i++) {// 将函数参数push进数组array[i+n] = arguments[i];}// 修正数组长度var new_length = n + m;array.length = new_length;// 返回值是数组的长度return new_length;
}

是的,整个push函数,并没有涉及是否是数组的问题。只关心了length。而因为其对象的特性,所以可以使用方括号来设置属性。

这也是万物皆类型和鸭子类型最生动的体现。

总结

JavaScript中的类数组的特殊性,是由其“万物皆类型”和“鸭子类型”决定的,而浏览器引擎底层的实现,更是佐证了这一点。
而先前说我的那位同学,因为只是知道类数组的几种表现和用法,并且想通过apply来打我脸,证明我根本没有仔细看书。这种行为不仅不友善,而且学习效率也不高。
因为,知其然而不知其所以然是不可取的。特别是发现很多这种例子,就得学会归纳总结。(感谢winter老师的演讲:一个前端的自我修养,教会我很多东西。)。
很多时候,深入看看源代码也会让你对这个理解的更透彻。将来就算是蹦出一百种类数组,也能知道是怎么回事儿。

最后,还是开头那句话:“都是搞技术的,和善一点不行吗?有问题就好好交流,不要总想着打别人脸啊…………”

最后附上本人博客地址和原文链接,希望能与各位多多交流。

Lxxyx的前端乐园
原文链接:深入理解JavaScript类数组

深入理解JavaScript类数组相关推荐

  1. 理解javascript类数组

    js数组,相比大家都很熟悉,因为走到哪都要用,但它有个"双胞胎弟弟" ,叫类数组(也叫伪数组),可能有的人了解,有的人不了解,今天我们来看一看. 什么是类数组 顾名思义,这玩意儿肯 ...

  2. JavaScript类数组对象参考

    JavaScript和DOM中有很多类数组对象,它们有以下特点 1.有length属性 2.可以使用[]通过下标访问 3.部分类数组对象使用[]访问成员时不只可以使用下标,还可以使用id或name 4 ...

  3. 多个数字数组_1分钟彻底理解JavaScript的数组与函数

    1 - 数组 1.1 数组的概念 - 数组可以把一组相关的数据一起存放,并提供方便的访问(获取)方式. - 数组是指**一组数据的集合**,其中的每个数据被称作**元素**,在数组中可以**存放任意类 ...

  4. 【JavaScript】类数组对象

    欢迎学习交流!!! 持续更新中- 文章目录 类数组对象 arguments 类数组对象 理解:是JS中一种特殊的对象.本质上来说对象是满足了一定条件的数组,类数组的使用目的在于使得一个对象既有数组的特 ...

  5. JavaScript类型化数组——ArrayBuffer

    JavaScript类型化数组是一种类似数组的对象,提供了一种用于访问原始二进制数据的机制. 类型化数组(Typed Array)很像C语言的数组,允许开发者以数组下标的形式,直接操作内存.有了类型化 ...

  6. JavaScript中的经典题型(类数组、CSS Sprites、事件委托、经典去重、原型链、闭包、深浅克隆、附带思路流程和源码)

    JavaScript中的经典题型 一.JavaScript中的经典题型 1..如何判断一个数组和类数组? 首先要明白什么是类数组. 类数组:类数组是一个普通对象,他的原型是Object.而真实的数组是 ...

  7. 对Javascript 类、原型链、继承的理解

    一.序言   和其他面向对象的语言(如Java)不同,Javascript语言对类的实现和继承的实现没有标准的定义,而是将这些交给了程序员,让程序员更加灵活地(当然刚开始也更加头疼)去定义类,实现继承 ...

  8. 重温 JavaScript 系列(2):数组去重、类数组转换数组

    在牛客上看到了一些汇总文章,这里总结一下JavaScript的数组去重解决方案: 假设测试数组: var arr = [1,1,2,2,3,'true','true',true,true,15,15, ...

  9. 【JavaScript】类数组详解

    [JavaScript]类数组详解 文章目录 [JavaScript]类数组详解 什么是类数组 类数组转换成数组 ES6 的方法转数组 callee属性 箭头函数没有arguments HTMLCol ...

最新文章

  1. mysql model first,一个支持 CodeFirst/DbFirst/ModelFirst 的数据库小工具
  2. Winforn中实现ZedGraph自定义添加右键菜单项(附源码下载)
  3. php 爬虫 类,php爬虫原型
  4. 华硕老毛子(Padavan)——L2TP连接自动重连解决方案
  5. python安装opencv出现错误,通过pip安装opencv时出错
  6. 电脑没有声音一键修复_电脑上有没有好用点的办公提醒小软件?有带声音提醒的桌面便签软件吗...
  7. 分层贝叶斯模型——结构
  8. Volatility内存分析工具-某即时通讯软件Windows端数据库密钥的分析
  9. PHPstudy配置局域网
  10. 完整版PayPal支付(java后端教程)
  11. Office365上启用Skype For Business并实现本地AD用户登录
  12. android马甲包代理,安卓渠道马甲包配置
  13. 计算机对体育专业就业前景,体育教育就业方向及就业前景分析
  14. date format picture ends before converting entire input string
  15. 短信或者邮件链接打开 APP(URL Scheme)
  16. Linux 奔腾4,我如何在Pentium 4计算机上安装Ubuntu 64位?
  17. 浏览器的input禁用输入法
  18. 简单的解决textarea文本框内容换行,对应到页面的内容也换行的问题
  19. zblogphp 广告联盟_天兴工作室:广告位大全插件(网站各种广告位集合效果)
  20. Android 6.0权限请求相关及权限分组

热门文章

  1. Redhat linux5.5下Oracle 10g 安装配置手册一
  2. 输入法图标(语言栏)不见了怎么办
  3. 沈阳建立通用航空产业基地,开辟国内首家无人机专用空域
  4. X86虚拟化之三种服务器虚拟化战略架构
  5. 今年端午节,想回家看看父母...
  6. MDaemon 10.1.2 通过Webclient发邮件时,提示“发邮件时发生错误
  7. Flex Timer 定时器
  8. WINDOWS SERVER 2003从入门到精通之“域控制器安全策略”打开错误的解决方法
  9. Mysql: pymysql 模块
  10. 启动jar包 服务方式