每个JS执行引擎都有自己的实现。这次我们主要关注V8引擎是如何实现数组的。本周主要精读文章《HowJavaScriptArrayWorksInternally?》,简单介绍了V8引擎的数组实现机制。笔者也会参考其他一些文章和源码进行说明。概述JS数组的内部类型有多种模式,如:PACKED_SMI_ELEMENTSPACKED_DOUBLE_ELEMENTSPACKED_ELEMENTSHOLEY_SMI_ELEMENTSHOLEY_DOUBLE_ELEMENTSHOLEY_ELEMENTSPACKED翻译为packed,其实就是“一个连续值的数组”;HOLEY译为洞,意思是数组有很多无效项,如holes,其实就是“洞的数组”的意思,这两个名词是互斥的。SMI表示数据类型是32位整型,DOUBLE表示浮点型,没有写类型,表示数组的类型也混杂了字符串,函数等,这里面的描述立场也是互斥的。所以可以这样看数组的内部类型:[PA??CKED,HOLEY]_[SMI,DOUBLE,'']_ELEMENTS。最高效的类型PACKED_SMI_ELEMENTS最简单的空数组类型默认为PACKED_SMI_ELEMENTS:constarr=[]//PACKED_SMI_ELEMENTSPACKED_SMI_ELEMENTS类型是最佳性能模式,存储类型默认为连续整数。当我们插入一个整数时,V8会自动扩展数组。这时候类型还是PACKED_SMI_ELEMENTS:constarr=[]//PACKED_SMI_ELEMENTSarr.push(1)//PACKED_SMI_ELEMENTS或者直接创建一个有内容的数组,也是这个类型:constarr=[1,2,3]//PACKED_SMI_ELEMENTSautomaticallydowngrades当我们使用数组操作时,V8会默默地降级类型。比如突然访问第100项:constarr=[1,2,3]//PACKED_SMI_ELEMENTSarr[100]=4//如果突然插入浮点类型,HOLEY_SMI_ELEMENTS会降级为DOUBLE:constarr=[1,2,3]//PACKED_SMI_ELEMENTSarr.push(4.1)//PACKED_DOUBLE_ELEMENTS当然,如果你结合这两个操作,HOLEY_DOUBLE_ELEMENTS将由你成功创建:constarr=[1,2,3]//PACKED_SMI_ELEMENTSarr[100]=4.1//HOLEY_DOUBLE_ELEMENTS比较狠,插入一个字符串或者函数,那么就是最底层的类型,HOLEY_ELEMENTS:constarr=[1,2,3]//PACKED_SMI_ELEMENTSarr[100]='4'//HOLEY_ELEMENTS是否有从Empty情况来看,PACKED>HOLEY的性能在Benchmark测试结果中快了大约23%。从类型上看,SMI>DOUBLE>空类型。原因是类型决定了数组中每一项的长度。DOUBLE类型意味着每一项可能是SMI或DOUBLE,而空类型的每一项的类型是完全不可确认的,额外的开销会花费在长度确认上。因此,HOLEY_ELEMENTS是性能最差的后备类型。降级的不可逆性文中提到了一个重要的点,就是说降级是不可逆的。详见下图:其实要表达的规律很简单,即PACKED只会变差HOLEY,而SMI只会变差DOUBLE和empty类型,而且这两个变化是不可逆的。精读为了验证文章的猜想,笔者使用v8-debug进行了调试。使用v8-debug调试介绍v8-debug,它是一个v8引擎调试工具,首先执行如下命令行安装jsvu:npmi-gjsvu然后执行jsvu,根据引导选择自己的系统类型,第二步选择Thejsenginetobeinstalled,选择v8和v8-debug:jsvu//选择macos//选择v8,v8-debug并创建一个js文件,比如test.js,然后通过~/.jsvu/v8-debug./test.js可以调试。默认不输出任何调试内容。我们根据需求添加参数来输出需要调试的信息,比如:~/.jsvu/v8-debug./test.js--print-ast这样就会把test.js文件的语法树打印出来。使用v8-debug调试数组内部实现为了观察数组内部实现,显然不能使用console.log(arr),我们需要使用%DebugPrint(arr)打印数组在debug模式下,而这个%DebugPrint函数是V8提供的NativeAPI,在普通的js脚本中是无法识别的,所以我们需要在执行的时候加上参数--allow-natives-syntax:~/.jsvu/v8-debug。/test.js--allow-natives-syntax同时,在test.js中使用%DebugPrint打印我们要调试的数组,如:constarr=[]%DebugPrint(arr)输出结果为:DebugPrint:0x120d000ca0b9:[JSArray]-map:0x120d00283a71