2

js 中你不知道的各种循环测速

 3 years ago
source link: https://www.xiabingbao.com/post/loop/js-loop-speed.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
在 js 中有着多种的循环方式,那么哪种循环方式最快,哪种循环方式最慢呢?我们最终应该选择哪种循环方式呢?

在前端 js 中,有着多种数组循环的方式:

  1. for 循环;

  2. while 和 do-while 循环;

  3. forEach、map、reduce、filter 循环;

  4. for-of 循环;

  5. for-in 循环;

那么哪种循环的执行速度最快呢,我们今天来看一看。

在测试循环速度之前,我们先来创建一个有 100 万数据的数组:

const len = 100 * 10000;
const arr = [];
for (let i = 0; i < len; i++) {
  arr.push(Math.floor(Math.random() * len));
}

测试环境为:

  • 电脑:iMac(10.13.6);

  • 处理器:4.2 GHz Intel Core i7;

  • 浏览器:Chrome(89.0.4389.82)

期待-蚊子的前端博客

1. for 循环

for 循环是我们最常用的一种循环方式了,最大的好处就是结构清晰,能够随时 break 停止。

我们先用 10 次的测试来看下:

console.log('test for');
for (let k = 0; k < 10; k++) {
  console.time('for');
  let sum = 0;
  for (let i = 0; i < len; i++) {
    sum += arr[i] % 100;
  }
  console.timeEnd('for');
}

最终得到的结果:

for循环的测试结果-蚊子的前端博客

在第 1 次和第 2 次时耗时比较多,从第 3 次开始就一直维持在 1.25ms 左右。

是真的-蚊子的前端博客

2. while 循环和 do-while 循环

这两个放在一起,也是他们的结构足够像,而且也能够随时 break 停止。

console.log('\ntest while');
for (let k = 0; k < 10; k++) {
  console.time('while');
  let sum = 0;
  let i = 0;
  while (i < len) {
    sum += arr[i] % 100;
    i++;
  }
  console.timeEnd('while');
}
console.log('\ntest do-while');
for (let k = 0; k < 10; k++) {
  console.time('do-while');
  let sum = 0;
  let i = 0;
  do {
    sum += arr[i] % 100;
    i++;
  } while (i < len);
  console.timeEnd('do-while');
}

while 循环和 do-while 循环的结果几乎一样,我们只看下 while 循环在浏览器上运行的结果:

while的测试结果-蚊子的前端博客

跟 for 循环的速度不相上下。

3. forEach、map 和 reduce 循环

接下来来到我们常用的数组三剑客了:forEach, map, reduce 等,这 3 个方法都是在 ES6 标准上新加的语法。

3.1 forEach 的简要介绍

这几种方法是无法停止循环的,无论使用break还是return,都无法停止整个循环。我们可以做一个测试,例如我想当遇到 3 的倍数时,即停止循环

[1, 2, 3, 4, 5].forEach((item) => {
  console.log(`before return: ${item}`);
  if (item % 3 === 0) {
    return;
  }
  console.log(`after return: ${item}`);
});

运行结果如下:

想要停止forEach循环-蚊子的前端博客

从运行的结果可以看到,我们的 return 只是没有执行当时循环时后面的语句,但并没有停止整个循环,后面的 4 和 5 依然正常输出。

真的假的-蚊子的前端博客

那循环是否真的像炫迈一样停不下来吗?并不,还有一种方式,可以停止循环。那就是抛出异常

try {
  [1, 2, 3, 4, 5].forEach((item) => {
    console.log(`before return: ${item}`);
    if (item % 3 === 0) {
      throw new Error('break forEach');
    }
    console.log(`after return: ${item}`);
  });
} catch (e) {}

在 forEach 中抛出异常后,就可以停止该循环,然后再使用try-catch捕获异常,避免整个服务被挂掉。

呦呦呦-蚊子的前端博客

虽然可以停止 forEach 的循环,但实现起来麻烦了不少。因此若没有停止整个循环的需求,可以使用 forEach, map 等循环方式;否则还是要使用其他的循环方式。

3.3 forEach 等的测速

好的,接下来我们就要测试这 3 个循环方式的循环速度了。

// forEach 的测试:
console.log('\ntest forEach');
for (let k = 0; k < 10; k++) {
  console.time('forEach');
  let sum = 0;
  arr.forEach((item) => {
    sum += item % 100;
  });
  console.timeEnd('forEach');
}
// map 的测试:
console.log('\ntest map');
for (let k = 0; k < 10; k++) {
  console.time('map');
  let sum = 0;
  arr.map((item) => {
    sum += item % 100;
  });
  console.timeEnd('map');
}
// reduce 的测试:
console.log('\ntest reduce');
for (let k = 0; k < 10; k++) {
  console.time('reduce');
  let sum = 0;
  arr.reduce((_, item) => {
    sum += item % 100;
  }, 0);
  console.timeEnd('reduce');
}

因这 3 个循环的时间差不多,我这里就只截取了 forEach 的测试结果。

forEach循环的测试结果-蚊子的前端博客

执行 10 次循环后,forEach 的执行时间差不多在 10.8ms 左右,比上面的 for 循环和 while 循环高了将近 10 倍的运行时间。

4. for-of

ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for...of 循环,作为遍历所有数据结构的统一的方法。

4.1 for-of 的简要介绍

一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历它的成员。也就是说,for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。

for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。

for-of 拿到的就是 value 本身,而 for-in 则拿到的是 key,然后通过 key 再获取到当前数据。

const fruits = ['apple', 'banana', 'orange', 'lemon'];

for (const value of fruits) {
  console.log(value); // 'apple', 'banana', 'orange', 'lemon'
}

喝点奶茶冷静下-蚊子的前端博客

4.2 for-of 的循环测速

测试 for-of 循环速度的代码:

console.log('\ntest for-of');
for (let k = 0; k < 10; k++) {
  console.time('for-of');
  let sum = 0;
  for (const value of arr) {
    sum += value % 100;
  }
  console.timeEnd('for-of');
}

测试结果:

for-of循环的测速结果-蚊子的前端博客

在多次重复同一个循环时,前 2 次的 for-of 循环时间会比较长,得在 15ms 以上。但后续的执行,循环速度就下降到 1.5ms 左右了,与 for 循环的时间差不多。

5. for-in 循环

for-in 通常用于 object 类型的循环,但也可以用来循环数组,毕竟所有数据类型的祖先都是 object 类型。

console.log('\ntest for-in');
for (let k = 0; k < 10; k++) {
  console.time('for-in');
  let sum = 0;
  for (let key in arr) {
    sum += arr[key] % 100;
  }
  console.timeEnd('for-in');
}

测试结果:

for-in循环的测速结果-蚊子的前端博客

for-in 循环的测速数据很惊人,简直是独一档的存在了,最好的时候也至少需要 136ms 的时间。可见 for-in 的循环效率真的很低。

真的狗-蚊子的前端博客

数组类型的数据还是不要使用采用 for-in 循环了;Object 类型的可以通过Object.values()先获取到所有的 value 数据,然后再使用 forEach 循环:

const obj = {};
for (let i = 0; i < len; i++) {
  obj[i] = Math.floor(Math.random() * len);
}
for (let k = 0; k < 10; k++) {
  console.time('forEach-values');
  let sum = 0;
  Object.values(obj).forEach((item) => {
    sum += item % 100;
  });
  console.timeEnd('forEach-values');
}

即使多了一步操作,循环时间也大概在 14ms 左右,要比 for-in 快很多。

我们把所有的循环数据放到一起对比一下,我们这里将每个循环的测试次数调整为 100 次,横轴是循环的次数,数轴是循环的时间:

所有循环的测速-蚊子的前端博客

  • for 循环、while 循环和 d-while 循环的时间最少;

  • for-of 循环的时间稍长;

  • forEach 循环、map 循环和 reduce 循环 3 者的数据差不多,但比 for-of 循环的时长更长一点;

  • for-in 循环所需要的时间最多;

每种循环的时长不一样,我们在选择循环方式时,除了考虑时间外,也要考虑到语义化和使用的场景。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK