【问题标题】:Should we use _.foreach() or better the native for of loop in TypeScript我们应该使用 _.foreach() 还是更好地使用 TypeScript 中的原生 for of 循环
【发布时间】:2016-03-09 09:22:35
【问题描述】:

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

现在在新项目中,他们在任何地方都使用 _.foreach() 循环来迭代数组。

我想知道的是,本机打字稿 for of 和 _.foreach() 之间是否存在性能差异

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

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

TypeScript 用于

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 中的“本机”,因为它对我来说更具可读性。

你们建议使用什么?是否还有其他点可以用于 of 或更好的 _.forEach

【问题讨论】:

    标签: performance typescript foreach lodash


    【解决方案1】:

    除了阅读之外,我对 typescript 没有任何经验,但我确实对 ES6/ES2015 有相当多的经验。 for of 过去和现在仍然是最终确定的 ES2015 规范的一部分。我会从 MDN 阅读 for of 上的 this 文章。

    以下是for offorEach 的一些相同点和不同点(这些都是我目前发现和知道的):

    • forEach in lodash 适用于数组、对象或 字符串。

    • native forEach 适用于数组、映射和集合。

    • for of 适用于所有 Iterables:数组、字符串、TypedArrays、 地图、集合、DOM 集合和生成器。

    我会从Exploring ES6 阅读for of 上的这一章(探索ES6 是一本很棒的读物。它非常透彻。它也是免费在线的。)其中的一些东西让我对for of 与众不同不在forEach 中。

    在 for-of 循​​环中中断并继续工作

    breakcontinue 不会在 forEach 中公开。您可以在 forEach 中最接近 continue 的方法是使用 return,这实际上几乎是一回事。至于break,虽然我没有其他选择(但不要打折 lodash,因为大部分需要休息的东西,比如查找和返回单个项目,都已经包含在 lodash 的大部分库中)。

    还应该注意的是,来自 async/await 的 await 关键字可在 for of 中使用,因为 forEach 使得阻止周围块等待 forEach 块中等待的 Promise 变得相当困难,但它是可以使用forEach,尽管使用mapreduce 可能会使等待比forEach 简单得多(取决于您对这些功能的熟悉程度)。下面是三个单独的等待承诺的实现,分别使用for offorEachreducemap,以查看可能的差异。

    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");
    });

    使用 ES2017 中的 Object.entries,您甚至可以轻松准确地迭代对象拥有可枚举的属性和值。如果你现在想使用它,你可以使用其中一种 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 的第一个参数默认为您在forfor of 循环中从let 获得的功能类型,这是一件好事。我的意思是,如果该迭代的变量内部发生任何异步操作,那么该迭代的范围将仅限于该循环的特定部分。 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 之间,您只需要决定哪一个最适合当前工作(例如,您需要breaks 还是需要使用地图、集合或生成器使用for of)。除此之外,我觉得对于它们都使用其核心功能进行操作的集合没有特别充分的理由。此外,在处理可以使用forEachfor of 的集合时,这主要取决于个人喜好,因为它们以大致相同的速度做同样的事情(根据解释器,速度可能随时变化)。我觉得 lodash 的特殊优势在于它的其他各种功能,实际上可以节省您自己编写代码的大量时间,例如 map、reduce、filter 和 find。既然你觉得写 for of 最舒服,我建议你继续这样写,但是一旦你开始用 lodash 使用它的其他功能写,你可能会开始觉得用 lodash 写更舒服。

    编辑:

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

    【讨论】:

      【解决方案2】:

      根据您的测试,我添加了另一个,使用本机 Array.prototype.forEach

      list.forEach(function(item) {
        console.log("" + item)
      });
      

      这实际上是我的首选方式,因为它实际上更容易打字。它也更接近您可能想用数组做的其他事情,例如map/filter

      请注意,http://jsperf.com/foreach-vs-forof/9 这三个都没有看似合理的性能差异。

      【讨论】:

        【解决方案3】:

        我无法评论 lodash,我没用过。但以下是一些可能有所帮助的背景。

        'For of' 是在 TypeScript 1.5 中引入的,用于循环每个元素,例如一个数组列表。如果您检查 JS 输出(并且取决于您是针对 ECMA 脚本 5 还是 6),您应该会发现在 ECMASCript5 的情况下,以下两者的输出将是相同的。 See this article for associated background reading 以及针对 ES6/2015 将如何影响输出。

        关于 ForEach 的 Typescript 实现,在 GitHub here 上有一个有趣的讨论。尤其是围绕条件跳出循环。

        for (let line of v.lineEntry) {
        
        }
        
        for (var _i = 0, list_1 = list; _i < list_1.length; _i++) { 
        
        }
        

        【讨论】:

          猜你喜欢
          • 2020-02-17
          • 2012-02-15
          • 1970-01-01
          • 2018-11-23
          • 2018-03-22
          • 2016-05-19
          • 2014-04-27
          • 2018-11-14
          • 2021-02-03
          相关资源
          最近更新 更多