performance - 我们应该使用 _.foreach() 还是更好地使用 TypeScript 中的原生 for of 循环

标签 performance typescript foreach lodash

我刚刚开始在一个使用 TypeScript 的新项目中工作。我来自另一个也使用 TypeScript 的项目。由于 TypeScript 中的原生 for of 循环可用,我们决定(旧项目团队)使用这个循环。尤其是对我来说,编写 for of 循环要方便得多,这与我的 Java 背景有关。

现在在新项目中,他们到处使用 _.foreach() 循环来迭代数组。

我想知道的是,of 和 _.foreach() 的原生 typescript 之间是否存在性能差异

我在 jsperf 中创建了一个小测试,它们的接缝速度或多或少完全相同......

https://jsperf.com/foreach-vs-forof/12

typescript For of

for (let num: string of list){
  console.log(num);
}

在 JavaScript 中
var list = "9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9".split();

//Transpiled TypeScript for of     | **19,937  ±5.04%
for (var _i = 0, list_1 = list; _i < list_1.length; _i++) {   
  var num = list_1[_i];   
  console.log("" + num); 
}

//lodash                           | 20,520  ±1.22%  
_.forEach(list, function(item) {
  console.log("" + item)
});

恕我直言,我更喜欢 TypeScript 中的“ native ”,因为它对我来说更具可读性。

大家建议用什么?是否还有其他点可以用于或更好的 _.forEach

最佳答案

除了阅读之外,我对 typescript 没有任何经验,但我对 ES6/ES2015 有相当多的经验。 for of曾经并且仍然是最终确定的 ES2015 规范的一部分。我会读 this关于 for of 的文章来自 MDN。

以下是 for of 的一些异同和 forEach (这些只是我目前发现和知道的):

  • forEach in lodash 适用于数组、对象或
    字符串。
  • 本地 forEach适用于数组、映射和集合。
  • for of适用于所有 Iterables :数组,字符串,TypedArrays,
    map 、集合、DOM 集合和生成器。

  • 我会在 for of 上阅读这一章来自 Exploring ES6 (Exploring ES6 是一本很好的读物。它非常彻底。它也是免费在线的。)其中的一些内容对我来说与 for of 不同。不在 forEach .

    break and continue work inside for-of loops


    breakcontinueforEach 中没有暴露.距离continue最近的地方在 forEach 中使用 return这实际上几乎是一样的。至于break虽然我看不到其他选择(但不要轻视 lodash,因为大多数需要休息的东西,比如查找和返回单个项目已经包含在 lodash 的大部分库中)。

    还应注意的是await来自 async/await 的关键字在 for of 中可用至于 forEach 使得阻止周围块等待 forEach 块内等待的 promise 变得相当困难,但是可以使用 forEach虽然使用 mapreduce可能使等待比 forEach 简单得多(取决于您对这些函数的熟悉程度)。以下是使用 for of 依次和并行等待 promise 的三个单独实现。 , forEach , 和 reducemap只是为了看到可能的差异。

    const timeout = ms => new Promise(res => setTimeout(() => res(ms), ms));
    
    const times = [100, 50, 10, 30];
    
    async function forOf() {
      console.log("running sequential forOf:");
      for (const time of times) {
        await timeout(time);
        console.log(`waited ${time}ms`);
      }
      console.log("running parallel forOf:");
      const promises = [];
      for (const time of times) {
        const promise = timeout(time).then(function(ms) {
          console.log(`waited ${ms}ms`);
        });
        promises.push(promise);
      }
      await Promise.all(promises);
    };
    
    
    async function forEach() {
      console.log("running sequential forEach:");
      let promise = Promise.resolve();
      times.forEach(function(time) {
        promise = promise.then(async function() {
          await timeout(time);
          console.log(`waited ${time}ms`);
        });
      });
      await promise;
      console.log("running parallel forEach:");
      const promises = [];
      times.forEach(function(time) {
        const promise = timeout(time).then(function(ms) {
          console.log(`waited ${ms}ms`);
        });
        promises.push(promise);
      });
      await Promise.all(promises);
    };
    
    async function reduceAndMap() {
      console.log("running sequential reduce:");
      const promise = times.reduce(function(promise, time) {
        return promise.then(async function() {
          await timeout(time);
          console.log(`waited ${time}ms`);
        });
      }, Promise.resolve());
      await promise;
      console.log("running parallel map:");
      const promises = times.map(async function(time) {
        const ms = await timeout(time)
        console.log(`waited ${ms}ms`);
      });
      await Promise.all(promises);
    }
    
    forOf().then(async function() {
      await forEach();
      await reduceAndMap();
    }).then(function() {
      console.log("done");
    });


    Object.entries在 ES2017 中,您甚至可以轻松准确地迭代对象拥有可枚举的属性和值。如果你现在想使用它,你可以使用 polyfills 之一 here .下面是一个示例。

    var obj = {foo: "bar", baz: "qux"};
        
    for (let x of Object.entries(obj)) { // OK
        console.log(x); // logs ["foo", "bar"] then ["baz", "qux"]
    }


    here是我写的一个快速 polyfill 的实现。您通常也会使用数组解构,它将键和值分离成它自己的变量,如下所示:

    var obj = {foo: "bar", baz: "qux"};
    
    for (let [key, val] of Object.entries(obj)) { // OK
        console.log(key + " " + val); // logs "foo bar" then "baz qux"
    }


    您也可以使用 Object.entriesforEach像这样:

    var obj = {foo: "bar", baz: "qux"};
    
    console.log("without array destructuring");
    Object.entries(obj).forEach((x) => { // OK
        const key = x[0], val = x[1];
        console.log(key + " " + val); // logs "foo bar" then "baz qux"
    });
    
    
    console.log("with array destructuring");
    Object.entries(obj).forEach(([key, val]) => { // OK
        console.log(key + " " + val); // logs "foo bar" then "baz qux"
    });

    forEach的第一个参数默认为您将从 let 获得的功能类型在 forfor of循环这是一件好事。我的意思是,如果该迭代的变量内部发生任何异步操作,则该迭代的范围将仅限于该循环的特定部分。 forEach 的这个属性实际上与 let 无关,而是与 JavaScript 中函数的作用域和闭包有关,而另一种选择是由于它们不是块作用域。例如,看看当使用 var 时会发生什么:

    const arr = [1,2,3,4,5,6,7,8,9];
    
    for(var item of arr) {
      setTimeout(() => {
        console.log(item);
      }, 100);
    }


    与何时let相反或 foreach 被使用。

    const arr = [1,2,3,4,5,6,7,8,9];
    const timeout = 100;
    
    console.log('for of');
    for(let item of arr) {
      setTimeout(() => {
        console.log(item);
      }, timeout);
    }
    
    setTimeout(() => {
      console.log('foreach');
      arr.forEach((item) => {
        setTimeout(() => {
          console.log(item);
        }, timeout);
      })
    }, timeout*arr.length);


    再次,我会注意到使用 var 之间的区别并使用 letforeach .不同之处在于 var 的变量被提升到函数作用域的顶部(或文件,如果它不在函数中),然后为整个作用域重新分配该值,因此循环到达其末尾并分配 item最后一次然后每一次 settimeout最后的函数日志 item .而与 letforeach变量 item不会被覆盖,因为 item作用域为块(使用 let 时)或函数(使用 foreach 时)。

    之间forEachfor of你只需要决定哪一个最适合当前的工作(例如,你需要 break s 还是需要使用 Maps、Sets 或 Generators 使用 for of )。除此之外,我觉得对于它们都使用其核心功能操作的集合,都没有特别强烈的理由。同样在处理可以使用 forEach 的集合时或 for of这主要取决于个人喜好,因为他们以大致相同的速度做同样的事情(并且速度可以根据口译员随时改变)。我觉得 lodash 的特殊优势在于它的其他各种功能,这些功能实际上可以为您节省大量时间,无需自己编写代码,例如 map、reduce、filter 和 find。因为你觉得最舒服的写作 for of我建议你继续这样写,但是一旦你开始使用 lodash 的其他函数编写它,你可能会开始觉得用 lodash 的方式写它更舒服。

    编辑:

    查看您的代码,我注意到您的列表创建错误。最后你只有 .split()你应该有 .split(",") .您正在创建整个字符串的长度为 1 的列表并在该字符串上迭代一次,这就是基准标记如此相似的原因。我重新进行了测试。 Here他们是。我仍然不会担心每次运行时它似乎都会改变的性能。

    关于performance - 我们应该使用 _.foreach() 还是更好地使用 TypeScript 中的原生 for of 循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35887490/

    相关文章:

    performance - 第一次查询 WMI 性能计数器时出现奇怪的延迟

    Typescript:使用导入时 Intellisense 找不到绝对路径

    Javascript 删除 forEach 中的元素

    php - 如何在php中将具有相同键的数组合并为一个

    java - Libgdx 游戏在重启时越来越慢

    c - 更有效的 flooring double 方法来获取数组索引

    typescript - 这两个 if 语句是否等价?

    node.js - 将 TypeScript 枚举与 Mongoose 模式一起使用

    c# - 你能在 C# 中乱序枚举一个集合吗?

    有很多模型的 wpf 3d