JS高效数据存取指南
我们经常会在JS脚本中对数据进行存取,平时我们都不太会去在意由于数据存取位置的不同而导致的脚本执行性能问题。其实,数据在脚本中存储的位置会直接影响脚本执行的总耗时。一般而言,在脚本中有4中地方可以存取数据:
- 字面量值
- 变量
- 数组元素
- 对象属性
读取数据总会带来性能开销,而开销大小取决于数据存储在这4种位置中的哪一种。下面我分别通过案例的形式对这4种位置在不同浏览器中的表现进行性能测试(本课程中测试均不考虑网络开销)。
测试工具为Visual Studio Code,运行环境基于node,浏览器(分别基于不同内核)为Chrome、360急速、火狐、IE11、Edge五种。
在大多数浏览器中,从字面量中读取值和从局部变量中读取值的开销差异很小,以至于可以忽略不计;你可以任意地混合使用字面量和局部变量,而无需担心性能问题。真正的差异在于从数组或对象中读取数据。存取这些数据结构中的某个值,需要通过索引(对于数组)或属性值(对于对象)来查询数据存储的位置。
经过测试,我们可以得出一些启示。
请看下面的代码:
谷歌中执行结果:
存数据共耗时:1339
火狐中执行结果:
存数据共耗时:1901
360急速浏览器执行结果:
存数据共耗时:1962
IE执行结果:
存数据共耗时:10925
Edge执行结果:
存数据共耗时:6961
这段代码多次读取data.count的值,乍一看在for循环中只读取了一次。然而实际读取data.count的次数应该是data.count的值加1,因为每次进入循环时都会重复执行控制语句(i<data.count).如果将值存储到局部变量中,然后从局部变量中存取,函数将运行得更快:
谷歌中执行结果:
存数据共耗时:1209
火狐中执行结果:
存数据共耗时:1891
360急速浏览器执行结果:
存数据共耗时:2054
IE执行结果:
存数据共耗时:7578
Edge执行结果:
存数据共耗时:6538
改写后的函数在执行过程中读取data.count仅有一次,这是因为这个函数一开始就把data.count存储到局部变量中。这样在函数其他位置就可以直接使用局部变量count,从而减少读取对象属性值的次数。由于减少了查找对象属性的次数,函数的执行效率将比之前的更高。
虽然从运行结果中来看,谷歌、火狐、Edge的性能提升不大,360急速甚至还略有下降,那是因为现代浏览器都对JS代码块的执行进行了相当力度的优化;而IE在执行速度上的表现最为明显,前后相差了4000毫秒左右。
随着数据结构深度的增加,它对数据存取速度的影响也跟着变大。例如,存取data.count比data.item.count快,存取data.item.count比data.item.subitem.count快。在处理属性时,点符号(.)的使用(用于查找属性)次数直接影响存取该属性的总耗时。
考虑如下代码:
以及如下代码:
谷歌、火狐、Edge、360急速浏览器耗时略有增加,IE中耗时有明显增加。
在处理HTMLCollection对象(比如getElementsByTagName这样的DOM方法的返回值,或element.childNodes这样的属性值)时使用局部变量特别重要。实际上每次存取HTMLCollection对象的属性值,都会对DOM文档进行动态查询。例如:
这段代码的第一行创建了一个查询来获取页面中所有的
总结
- 从字面量和从局部变量读取值开销差异不大;
- 在数据存取时,将函数中使用超过一次的对象属性或数组元素存储为局部变量是一种好方法;
- 数据结构深度越深,存取时也越慢;
- 存取对象使用点符号(data.count)还是方括号(data['count'])几乎没有区别;
- 操作DOM对象的开销总是比非DOM对象要大;
- 现代浏览器会对我们写的JS代码进行重新编译,做一些性能优化;
- IE真的很慢,怪得不会被微软淘汰;
- 养成良好的代码编写习惯,不要过度依赖工具。