往数组⾥添加键值对_JavaScript数组的特别之处
数组是前端开发者最常⽤的数据结构了,我们在项⽬中⽆时不刻在操作着数组,例如将列表组件的数据储存在数组⾥、将需要渲染成条形图的数据同样储存在⼀个数组⾥,虽然我们经常使⽤数组,但是很多⼈并不了解JavaScript数组的本质。
本节我们将从JavaScript数组的使⽤、内存模型两⼤部分进⾏讲解,希望通过这个⼩节,让⼤家对JavaScript的数组有更深的认识。
在正式开始这节之前,请⼤家思考⼀个问题,JavaScript的数组有什么特殊之处?
数组的使⽤
数组是我们最常⽤的数据结构,很多基于数组的操作⼤家也⾜够熟悉了,我们不会在这⾥罗列数组的API,因为MDN数组这⼀部分⾜够权威也⾜够全⾯,我们会简单介绍下重点的数组⽅法,为接下来的内容做铺垫。
我⾃⼰是⼀名从事了多年开发的web前端⽼程序员,⽬前辞职在做⾃⼰的web前端私⼈定制课程,今年年初我花了⼀个⽉整理了⼀份最适合2019年学习的web前端学习⼲货,各种框架都有整理,送给每⼀位前端⼩伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。
数组的创建与初始化
如果你之前学过其它语⾔类似于c++/java等,你可能会⽤以下⽅法创建并初始化⼀个数组:
const appleMac = new Array('Mac Book Air', 'iMac', 'Mac Book Pro', 'Mac pro')
当然这在JavaScript中是可以的,但并不主流⽅法,通常⼈们创建并初始化数组⽤的是字⾯量的⽅式:
const appleMac = ['Mac Book Air', 'iMac', 'Mac Book Pro', 'Mac pro']
在es6中引⼊了两个新⽅法,同样可以创建数组:
Array.of() 返回由所有参数组成的数组,不考虑参数的数量或类型,如果没有参数就返回⼀个空数组
Array.from()从⼀个类数组或可迭代对象中创建⼀个新的数组
这两个⽅法分别解决了两个问题,Array.of()解决了构造函数⽅法创建数组时单个数字引起了怪异⾏为。
const a = new Array(3); // (3) [empty × 3] 构造函数⽅法单个数组会被⽤于数组长度const b = Array.of(3); // [3]
Array.from()解决了『类数组』的转化问题,之前我们将类数组转化为数组的⽅法普遍⽤的是Array.prototype.slice.call(arguments)这种偏Hack的⽅法,Array.from()的出现将其规范化,在以后的转化中我们最好按照标准的Array.from()⽅法进⾏转化。
数组的操作
数组的操作有数⼗种之多,我们不可能⼀⼀讲到,具体使⽤也可以看MDN,我们只讲两个对本节⽐较重要的api。
向头部插⼊元素
unshift操作是最常见的向数组头部添加元素的操作
const arr = [1, 2, 3]arr.unshift(0) // arr = [0, 1, 2, 3,]
向尾部插⼊元素
push操作是最常见的向数组尾部添加元素的操作
const arr = [1, 2, 3]arr.push(4) // arr = [1, 2, 3, 4]
内存模型
编程语⾔的内存通常要经历三个阶段
1. 分配内存
2. 对内存进⾏读、写
3. 释放内存(垃圾回收)
数组的创建对应着第⼀阶段,数组的操作对应着第⼆阶段。
因此,现在有⼀个问题,我们分别⽤push和unshift往数组的尾部和头部添加元素,谁的速度更快?
连续内存
如果你⽐较了解相关数据结构内存的话应该会知道,数组是会被分配⼀段连续的内存,如图:
内存分布
那么当我们向这个数组最后push元素6的时候,只需要将后⾯的⼀块内存分配给6即可。
⽽unshift则不同,因为是向数组头部添加元素,数组为了保证连续性,头部之后的元素需要依次向后移动。
unshift的本质类似于下⾯的代码:
for (var i=numbers.length; i>=0; i--){ numbers[i] = numbers[i-1]; } numbers[0] = -1;
内存分布
由于unshift 触发了所有元素内存后移,导致性能远⽐push要差。
我在node10.x版本下做了⼀个实验:
function unshiftFn() { const a = [] console.time('unshift') for (var i=0;i<100000;i++) { a.unshift(1); } console.timeEnd('unshift')}function pushFn() { const a = [
我们看见两者的速度差了⾮常多,⽽且如果你不断调整for循环的次数,会发现当次数越多的时候,unshift操作就越慢,因为需要往后移的元素也就越多。
⽽造成这个差异的正是因为数组是被储存为⼀块连续内存导致的,这就造成了数组的『插⼊』『删除』的性能都很差,因为我们⼀旦删除或者插⼊元素,其他元素为了保持⼀块连续的内存都不得不产⽣⼤量元素位移,这是性能的杀⼿。
⾮连续内存
我们开头就有⼀个问题:JavaScript的数组有什么特殊之处?
当然我们会说很多JavaScript的特殊之处,什么⽀持字⾯量声明创建,⽀持储存不同类型数据、动态性等等。
⽽本质上JavaScript数组的特殊之处在于JavaScript的数组不⼀定是连续内存。
⽽关于数组的定义:
在计算机科学中,数组数据结构(英语:array data structure),简称数组(英语:Array),是由相同类型的元素(element)的集合所组成的数据结构,分配⼀块连续的内存来存储。
如果是这样的话,JavaScript的数组似乎并不是严格意义上的数组,那么为什么上⼀⼩节说数组是分配了连续内存呢?这不是⾃相⽭盾了吗?
JavaScript的数组是否分配连续内存取决于数组成员的类型,如果统⼀是单⼀类型的数组那么会分配连续内存,如果数组内包括了各种各样的不同类型,那么则是⾮连续内存。
⾮连续内存的数组⽤的是类似哈希映射的⽅式存在,⽐如声明了⼀个数组,他被分配给了1001、2011、1088、1077四个⾮连续的内存地址,通过指针连接起来形成⼀个线性结构,那么当我们查询某元素的时候其实是需要遍历这个线性链表结构的,这⼗分消耗性能。
数组地址
⽽线性储存的数组只需要遵循这个寻址公式,进⾏数学上的计算就可以到对应元素的内存地址。
a[k]_address = base_address + k * type_size
我们做⼀个简单的实验,我们不断向数组插⼊元素,但对⽐的双⽅是⾮线性储存的数组和线性储存的同构数组:
const total = 1000000function unshiftContinuity() { const arr = new Array(total) arr.push({name: 'xiaomuzhu'}); console.time('unshiftContinuity') for(let i=0;i 我们看到,⾮线性储存的数组其速度⽐线性储存的数组要慢得多。
javascript全局数组由于作者并没有阅读过JavaScript引擎的源码,所以这并不是⼀⼿资料,如果有错误⾮常欢迎指出来,我会及时更正。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论