深入理解JavaScript类数组
起因
写这篇博客的起因,是我在知乎上回答一个问题时,说自己在学前端时把《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类数组相关推荐
- 理解javascript类数组
js数组,相比大家都很熟悉,因为走到哪都要用,但它有个"双胞胎弟弟" ,叫类数组(也叫伪数组),可能有的人了解,有的人不了解,今天我们来看一看. 什么是类数组 顾名思义,这玩意儿肯 ...
- JavaScript类数组对象参考
JavaScript和DOM中有很多类数组对象,它们有以下特点 1.有length属性 2.可以使用[]通过下标访问 3.部分类数组对象使用[]访问成员时不只可以使用下标,还可以使用id或name 4 ...
- 多个数字数组_1分钟彻底理解JavaScript的数组与函数
1 - 数组 1.1 数组的概念 - 数组可以把一组相关的数据一起存放,并提供方便的访问(获取)方式. - 数组是指**一组数据的集合**,其中的每个数据被称作**元素**,在数组中可以**存放任意类 ...
- 【JavaScript】类数组对象
欢迎学习交流!!! 持续更新中- 文章目录 类数组对象 arguments 类数组对象 理解:是JS中一种特殊的对象.本质上来说对象是满足了一定条件的数组,类数组的使用目的在于使得一个对象既有数组的特 ...
- JavaScript类型化数组——ArrayBuffer
JavaScript类型化数组是一种类似数组的对象,提供了一种用于访问原始二进制数据的机制. 类型化数组(Typed Array)很像C语言的数组,允许开发者以数组下标的形式,直接操作内存.有了类型化 ...
- JavaScript中的经典题型(类数组、CSS Sprites、事件委托、经典去重、原型链、闭包、深浅克隆、附带思路流程和源码)
JavaScript中的经典题型 一.JavaScript中的经典题型 1..如何判断一个数组和类数组? 首先要明白什么是类数组. 类数组:类数组是一个普通对象,他的原型是Object.而真实的数组是 ...
- 对Javascript 类、原型链、继承的理解
一.序言 和其他面向对象的语言(如Java)不同,Javascript语言对类的实现和继承的实现没有标准的定义,而是将这些交给了程序员,让程序员更加灵活地(当然刚开始也更加头疼)去定义类,实现继承 ...
- 重温 JavaScript 系列(2):数组去重、类数组转换数组
在牛客上看到了一些汇总文章,这里总结一下JavaScript的数组去重解决方案: 假设测试数组: var arr = [1,1,2,2,3,'true','true',true,true,15,15, ...
- 【JavaScript】类数组详解
[JavaScript]类数组详解 文章目录 [JavaScript]类数组详解 什么是类数组 类数组转换成数组 ES6 的方法转数组 callee属性 箭头函数没有arguments HTMLCol ...
最新文章
- mysql model first,一个支持 CodeFirst/DbFirst/ModelFirst 的数据库小工具
- Winforn中实现ZedGraph自定义添加右键菜单项(附源码下载)
- php 爬虫 类,php爬虫原型
- 华硕老毛子(Padavan)——L2TP连接自动重连解决方案
- python安装opencv出现错误,通过pip安装opencv时出错
- 电脑没有声音一键修复_电脑上有没有好用点的办公提醒小软件?有带声音提醒的桌面便签软件吗...
- 分层贝叶斯模型——结构
- Volatility内存分析工具-某即时通讯软件Windows端数据库密钥的分析
- PHPstudy配置局域网
- 完整版PayPal支付(java后端教程)
- Office365上启用Skype For Business并实现本地AD用户登录
- android马甲包代理,安卓渠道马甲包配置
- 计算机对体育专业就业前景,体育教育就业方向及就业前景分析
- date format picture ends before converting entire input string
- 短信或者邮件链接打开 APP(URL Scheme)
- Linux 奔腾4,我如何在Pentium 4计算机上安装Ubuntu 64位?
- 浏览器的input禁用输入法
- 简单的解决textarea文本框内容换行,对应到页面的内容也换行的问题
- zblogphp 广告联盟_天兴工作室:广告位大全插件(网站各种广告位集合效果)
- Android 6.0权限请求相关及权限分组
热门文章
- Redhat linux5.5下Oracle 10g 安装配置手册一
- 输入法图标(语言栏)不见了怎么办
- 沈阳建立通用航空产业基地,开辟国内首家无人机专用空域
- X86虚拟化之三种服务器虚拟化战略架构
- 今年端午节,想回家看看父母...
- MDaemon 10.1.2 通过Webclient发邮件时,提示“发邮件时发生错误
- Flex Timer 定时器
- WINDOWS SERVER 2003从入门到精通之“域控制器安全策略”打开错误的解决方法
- Mysql: pymysql 模块
- 启动jar包 服务方式