JavaScript 数组的进化与性能分析

正式开始前需要声明,本文并不是要讲解 JavaScript 数组基础知识,也不会涉及语法和使用案例。本文讲得更多的是内存、优化、语法差异、性能、近来的演进。

在使用 JavaScript 前,我对 C、C++、C# 这些已经颇为熟悉。与许多 C/C++ 开发者一样,JavaScript 给我的第一印象并不好。

Array 是主要原因之一。JavaScript 数组不是连续(contiguous)的,其实现类似哈希映射(hash-maps)或字典(dictionaries)。我觉得这有点像是一门 B 级语言,数组实现根本不恰当。自那以后,JavaScript 和我对它的理解都发生了变化,很多变化。

为什么说 JavaScript 数组不是真正的数组

在聊 JavaScript 之前,先讲讲 Array 是什么。

数组是一串连续的内存位置,用来保存某些值。注意重点,“连续”(continuous,或 contiguous),这很重要。

上图展示了数组在内存中存储方式。这个数组保存了 4 个元素,每个元素 4 字节。加起来总共占用了 16 字节的内存区。

假设我们声明了 tinyInt arr[4];,分配到的内存区的地址从 1201 开始。一旦需要读取 arr[2],只需要通过数学计算拿到 arr[2] 的地址即可。计算 1201 + (2 X 4),直接从 1209 开始读取即可。

JavaScript 中的数据是哈希映射,可以使用不同的数据结构来实现,如链表。所以,如果在 JavaScript 中声明一个数组 var arr = new Array(4),计算机将生成类似上图的结构。如果程序需要读取 arr[2],则需要从 1201 开始遍历寻址。

以上急速 JavaScript 数组与真实数组的不同之处。显而易见,数学计算比遍历链表快。就长数组而言,情况尤其如此。

JavaScript 数组的进化

不知你是否记得我们对朋友入手的 256MB 内存的电脑羡慕得要死的日子?而今天,8GB 内存遍地都是。

与此类似,JavaScript 这门语言也进化了不少。从 V8、SpiderMonkey 到 TC39 和与日俱增的 Web 用户,巨大的努力已经使 JavaScript 成为世界级必需品。一旦有了庞大的用户基础,性能提升自然是硬需求。

实际上,现代 JavaScript 引擎是会给数组分配连续内存的 —— 如果数组是同质的(所有元素类型相同)。优秀的程序员总会保证数组同质,以便 JIT(即时编译器)能够使用 c 编译器式的计算方法读取元素。

不过,一旦你想要在某个同质数组中插入一个其他类型的元素,JIT 将解构整个数组,并按照旧有的方式重新创建。

因此,如果你的代码写得不太糟,JavaScript Array 对象在幕后依然保持着真正的数组形式,这对现代 JS 开发者来说极为重要。

此外,数组跟随 ES2015/ES6 有了更多的演进。TC39 决定引入类型化数组(Typed Arrays),于是我们就有了 ArrayBuffer。

ArrayBuffer 提供一块连续内存供我们随意操作。然而,直接操作内存还是太复杂、偏底层。于是便有了处理 ArrayBuffer 的视图(View)。目前已有一些可用视图,未来还会有更多加入。

var buffer = new ArrayBuffer(8);
var view  = new Int32Array(buffer);
view[0] = 100;

了解更多关于类型化数组(Typed Arrays)的知识,请访问 MDN 文档。

高性能、高效率的类型化数组在 WebGL 之后被引入。WebGL 工作者遇到了极大的性能问题,即如何高效处理二进制数据。另外,你也可以使用 SharedArrayBuffer 在多个 Web Worker 进程之间共享数据,以提升性能。

从简单的哈希映射到现在的 SharedArrayBuffer,这相当棒吧?

旧式数组 vs 类型化数组:性能

前面已经讨论了 JavaScript 数组的演进,现在来测试现代数组到底能给我们带来多大收益。下面是我在 Mac 上使用 Node.js 8.4.0 进行的一些微型测试结果。

旧式数组:插入

var LIMIT = 10000000;
var arr = new Array(LIMIT);
console.time("Array insertion time");
for (var i = 0; i < LIMIT; i++) {
arr[i] = i;
}
console.timeEnd("Array insertion time");

用时:55ms

Typed Array:插入
var LIMIT = 10000000;
var buffer = new ArrayBuffer(LIMIT * 4);
var arr = new Int32Array(buffer);
console.time("ArrayBuffer insertion time");
for (var i = 0; i < LIMIT; i++) {
arr[i] = i;
}
console.timeEnd("ArrayBuffer insertion time");

用时:52ms

擦,我看到了什么?旧式数组和 ArrayBuffer 的性能不相上下?不不不。请记住,前面提到过,现代编译器已经智能化,能够将元素类型相同的传统数组在内部转换成内存连续的数组。第一个例子正是如此。尽管使用了 new Array(LIMIT),数组实际依然以现代数组形式存在。

接着修改第一例子,将数组改成异构型(元素类型不完全一致)的,来看看是否存在性能差异。

旧式数组:插入(异构)
var LIMIT = 10000000;
var arr = new Array(LIMIT);
arr.push({a: 22});
console.time("Array insertion time");
for (var i = 0; i < LIMIT; i++) {
arr[i] = i;
}
console.timeEnd("Array insertion time");

用时:1207ms

改变发生在第 3 行,添加一条语句,将数组变为异构类型。其余代码保持不变。性能差异表现出来了,慢了 22 倍。

旧式数组:读取

var LIMIT = 10000000;
var arr = new Array(LIMIT);
arr.push({a: 22});
for (var i = 0; i < LIMIT; i++) {
arr[i] = i;
}
var p;
console.time("Array read time");
for (var i = 0; i < LIMIT; i++) {
//arr[i] = i;
p = arr[i];
}
console.timeEnd("Array read time");

用时:196ms

Typed Array:读取
var LIMIT = 10000000;
var buffer = new ArrayBuffer(LIMIT * 4);
var arr = new Int32Array(buffer);
console.time("ArrayBuffer insertion time");
for (var i = 0; i < LIMIT; i++) {
arr[i] = i;
}
console.time("ArrayBuffer read time");
for (var i = 0; i < LIMIT; i++) {
var p = arr[i];
}
console.timeEnd("ArrayBuffer read time");

用时:27ms

结论

类型化数组的引入是 JavaScript 发展历程中的一大步。Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,这些是类型化数组视图,使用原生字节序(与本机相同)。我们还可以使用 DataView 创建自定义视图窗口。希望未来会有更多帮助我们轻松操作 ArrayBuffer 的 DataView 库。

JavaScript 数组的演进非常 nice。现在它们速度快、效率高、健壮,在内存分配时也足够智能。

总结

以上所述是小编给大家介绍的JavaScript 数组的进化与性能分析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • javascript 三种数组复制方法的性能对比

    一. 三种数组复制方法 1. by slice var arr = [1, 2, 3], copyArr; copyArr = arr.slice(); 2. by concat var arr = [1, 2, 3], copyArr; copyArr = arr.concat(); 3. by loop var arr = [1, 2, 3], copyArr = []; for (var i=0, j=arr.length; i 二. 测试环境 浏览器: IE6+, FF 3.5.5, O

  • JS往数组中添加项性能分析

    比较了4种可以向数组添加项的方法之间的性能: 使用索引器添加 复制代码 代码如下: console.time("index"); var a = []; for (var i = 0, l = times; i < l; i++) {     a[i] = i; } console.timeEnd("index"); 使用push方法 复制代码 代码如下: console.time("push"); var a = []; for (var

  • 探讨js字符串数组拼接的性能问题

    我们知道,在js中,字符串连接是性能最低的操作之一. 例如: 复制代码 代码如下: var text="Hello";  text+=" World!"; 早期浏览器没有对这种运算进行优化. 由于字符串是不可变的,这意味着要创建中间字符串来存储连接的结果.频繁地在后台创建和销毁字符串导制性能异常低下. 因此,可以利用数组对象进行优化. 例如: var buffer=[],i=0; buffer[i++]="Hello"; //通过相应索引值添加元

  • javascript数组去重3种方法的性能测试与比较

    昨天参加的一个前端面试,其中有一题数组去重,首先想到的是对象存键值的方法,代码如下 方法一:(简单存键值) 复制代码 代码如下: Array.prototype.distinct1 = function() { var i=0,tmp={},that=this.slice(0) this.length=0; for(;i<that.length;i++){ if(!(that[i] in tmp)){ this[this.length]=that[i]; tmp[that[i]]=true; }

  • 小议Function.apply()之二------利用Apply的参数数组化来提高 JavaScript程序性能

    我们再来聊聊Function.apply() 在提升程序性能方面的技巧. 我们先从 Math.max() 函数说起,  Math.max后面可以接任意个参数,最后返回所有参数中的最大值. 比如  alert(Math.max(5,8))   //8alert(Math.max(5,7,9,3,1,6))   //9 但是在很多情况下,我们需要找出数组中最大的元素.var arr=[5,7,9,1]alert(Math.max(arr))    //   这样却是不行的.一定要这样写 functi

  • 数组方法解决JS字符串连接性能问题有争议

    1.传统上,字符串连接一直是js中性能最低的操作之一. var text="Hello"; text+=" World!"; 早期浏览器没有对这种运算进行优化. 由于字符串是不可变的,这意味着要创建中间字符串来存储连接的结果.频繁地在后台创建和销毁字符串导制性能异常低下. 2.发现这一点后,开发者们利用数组对象进行优化. var buffer=[],i=0; buffer[i++]="Hello";//通过相应索引值添加元素比push方法快 bu

  • JavaScript 数组的进化与性能分析

    正式开始前需要声明,本文并不是要讲解 JavaScript 数组基础知识,也不会涉及语法和使用案例.本文讲得更多的是内存.优化.语法差异.性能.近来的演进. 在使用 JavaScript 前,我对 C.C++.C# 这些已经颇为熟悉.与许多 C/C++ 开发者一样,JavaScript 给我的第一印象并不好. Array 是主要原因之一.JavaScript 数组不是连续(contiguous)的,其实现类似哈希映射(hash-maps)或字典(dictionaries).我觉得这有点像是一门

  • JS常用的几种数组遍历方式以及性能分析对比实例详解

    本文实例讲述了JS常用的几种数组遍历方式以及性能分析对比.分享给大家供大家参考,具体如下: 前言 这一篇与上一篇 JS几种变量交换方式以及性能分析对比 属于同一个系列,本文继续分析JS中几种常用的数组遍历方式以及各自的性能对比 起由 在上一次分析了JS几种常用变量交换方式以及各自性能后,觉得这种方式挺好的,于是抽取了核心逻辑,封装成了模板,打算拓展成一个系列,本文则是系列中的第二篇,JS数组遍历方式的分析对比 JS数组遍历的几种方式 JS数组遍历,基本就是for,forin,foreach,fo

  • 三种Golang数组拷贝方式及性能分析详解

    目录 测试 测试代码 测试结果 原理分析 copy append 总结 在Go语言中,我们可以使用for.append()和copy()进行数组拷贝,对于某些对性能比较敏感且数组拷贝比较多的场景,我们可以会对拷贝性能比较关注,这篇文件主要是对比一下这三种方式的性能. 测试 测试条件是把一个64KB的字节数组分为64个块进行复制. 测试代码 package test import ( "testing" ) const ( blocks = 64 blockSize = 1024 ) v

  • JavaScript数组各种常见用法实例分析

    本文实例讲述了JavaScript数组各种常见用法.分享给大家供大家参考.具体如下: 运行效果如下图所示: 具体代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/x

  • Javascript数组中push方法用法分析

    本文实例讲述了Javascript数组中push方法用法.分享给大家供大家参考,具体如下: 看下面代码: var o = { 1:'a' ,2:'b' ,length:2 ,push:Array.prototype.push }; o.push('c'); Q:o现在内部的值是什么样子? 我的第一反应是排斥,为什么要研究不合理情况下[解释引擎]的行为?但是这种推论有时候又很吸引人,于是我回来的时候仔细思考了下,发现其实很简单. 对于push这个方法,我条件反射地想到的就是栈,[数据结构的经典栈]

  • javascript数组遍历的方法实例分析

    本文实例讲述了javascript数组遍历的方法.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> var a = [1,2,3,4,5,6]; var b = a.

  • Javascript数组与字典用法分析

    本文实例分析了Javascript数组与字典用法.分享给大家供大家参考.具体分析如下: Javascript 的数组Array,既是一个数组,也是一个字典(Dictionary). 先举例看看数组的用法. 复制代码 代码如下: var a = new Array();  a[0] = "Acer";  a[1] = "Dell";  for (var i in a) {      alert(i);          } 上面的代码创立了一个数组,每个元素都是一个字

  • JavaScript数组方法总结分析

    由于最近都在freecodecamp上刷代码,运用了很多JavaScript数组的方法,因此做了一份关于JavaScript教程的整理,具体内容如下: 一.普通方法 1.join() 将数组元素连接在一起,并以字符串形式返回 参数:可选,指定元素之间的分隔符,没有参数则默认为逗号 返回值:字符串 对原数组的影响:无 2.reverse()将数组的元素顺序变成倒序返回 参数:无 返回值:数组 对原数组的影响:原数组被修改为倒序排列之后的数组 3.sort()对数组元素进行排序并返回 参数:可选,排

  • JavaScript类数组对象转换为数组对象的方法实例分析

    本文实例分析了JavaScript类数组对象转换为数组对象的方法.分享给大家供大家参考,具体如下: 1.类数组对象: 拥有length属性,可以通过下标访问: 不具有数组所具有的方法. 2.为什么要将类数组对象转换为数组对象? 数组对象Array有很多方法:shift.unshift.splice.slice.concat.reverse.sort,ES6又新增了一些方法:forEach.isArray.indexOf.lastIndexOf.every.some.map.filter.redu

随机推荐