前言

在我们编写程序的过程中,遍历是常有的需求,如果对应的集合较小,不同遍历的方式之间的差距可能不会很大,可如果对于一个大型集合来说,选择对的遍历方法就至关重要了,尤其是对于浏览器这种对于时间及其敏感的环境。

本篇文章将会针对JavaScript中数组和对象这两种基本的数据结构的各个内置遍历方法进行分析。

遍历方式

  • 数组遍历方式
    • for 循环
    • for 循环且缓存数组长度
    • forEach
    • while循环
    • for-in
    • for-of
  • 对象的遍历方式
    • for-in
    • Object.keys + for循环
    • Object.keys + for-of
    • Object.keys + forEach

测试代码

  1. function getRunTime(func) {
  2. const now = performance.now()
  3. func()
  4. return performance.now() - now
  5. }
  6. var arr = createArrayWithLen(1000000)
  7. var obj = createObjWithLen(100000)
  8. var obj1 = createObjWithLen(10000)
  9. Object.setPrototypeOf(obj, obj1)
  10. function createObjWithLen(len) {
  11. const obj = {}
  12. while(len) {
  13. obj[Math.random()] = len
  14. len--
  15. }
  16. return obj
  17. }
  18. function createArrayWithLen(len) {
  19. const arr = []
  20. while(len--) {
  21. arr.push(Math.random())
  22. }
  23. return arr
  24. }
  25. function objForIn() {
  26. ret = 0
  27. for (let i in obj) {
  28. if (!obj.hasOwnProperty(i)) continue
  29. ret += obj[i]
  30. }
  31. }
  32. function objKeysForLoop() {
  33. ret = 0
  34. const keys = Object.keys(obj)
  35. for (let i = 0; i < keys.length; i++) {
  36. ret += obj[keys[i]]
  37. }
  38. }
  39. function objKeysForOf() {
  40. ret = 0
  41. for (let key of Object.keys(obj)) {
  42. ret += obj[key]
  43. }
  44. }
  45. function objKeysForEach() {
  46. ret = 0
  47. const keys = Object.keys(obj)
  48. keys.forEach(key => {
  49. ret += obj[key]
  50. })
  51. }
  52. function forLoop() {
  53. ret = 0
  54. for (let i = 0; i < arr.length; i++) {
  55. ret += arr[i]
  56. }
  57. }
  58. function forOf() {
  59. ret = 0
  60. for (let i of arr) {
  61. ret += i
  62. }
  63. }
  64. function forLoopWithCache() {
  65. ret = 0
  66. for (let i = 0, len = arr.length; i < len; i++) {
  67. ret += arr[i]
  68. }
  69. }
  70. function whileLoop() {
  71. ret = 0
  72. let i = 0
  73. while (i < arr.length) {
  74. ret += arr[i++]
  75. }
  76. }
  77. function forEach() {
  78. ret = 0
  79. arr.forEach(item => {
  80. ret += item
  81. })
  82. }
  83. function forIn() {
  84. ret = 0
  85. for (let i in arr) {
  86. if (!obj.hasOwnProperty(i)) continue
  87. ret += arr[i]
  88. }
  89. }
  90. function average(times, func, ...args) {
  91. let ret = 0
  92. let len = times
  93. while(len) {
  94. ret += func(...args)
  95. len--
  96. }
  97. return ret / times
  98. }
  99. function runTest(options) {
  100. const result = Object.keys(options).reduce((acc, key) => {
  101. acc[key] = average(10, getRunTime, options[key])
  102. return acc
  103. }, {})
  104. console.table(result)
  105. }
  106. runTest({
  107. forLoop,
  108. forLoopWithCache,
  109. whileLoop,
  110. forOf,
  111. forEach,
  112. forIn,
  113. objForIn,
  114. objKeysForLoop,
  115. objKeysForOf,
  116. objKeysForEach
  117. })

结论

单个测试进行十次取平均值
数组遍历(1000000个元素) 时间(ms)
forLoop 11.090499999772874
forLoopWithCache 10.3529999996681
whileLoop 10.647999999855529
forOf 20.10000000045693
forEach 25.5765000001702
forIn 243.2185000008758
对象遍历(100000个键值对+1000键值对的原型) 时间(ms)
objForIn 37.153500000567874
objKeysForLoop 20.723500000167405
objKeysForOf 19.997999999759486
objKeysForEach 20.322000000305707
  • 对于数组遍历来说:for≈while < for-of < forEach << for-in。
    • for循环无论缓不缓存数组长度影响不大
    • forEach的遍历性能比不上普通的for和while循环,不过差距不大
    • 避免使用for-in遍历数组,由于需要遍历原型链,性能极差
  • 对于对象来说:
    • 对于普通对象来说,四者区别不大
    • 对于原型不是Object.prototype的对象,由于需要遍历原型链,for-in的性能比起Object.keys遍历性能会较为差一点