我们应该在 TypeScript 中使用 _.foreach() 还是更好的原生 for of 循环
Should we use _.foreach() or better the native for of loop in TypeScript
我刚开始在一个使用 TypeScript 的新项目中工作。我来自另一个也使用 TypeScript 的项目。由于 TypeScript 中的原生 for of 循环是可用的,我们决定(旧项目团队)使用这个。特别是对我来说,编写 for of 循环要方便得多,这与我的 java 背景有关。
现在在新项目中,他们到处都使用 _.foreach() 循环来遍历数组。
我想知道的是,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 的 "native" for of of,因为它对我来说更具可读性。
你们建议使用什么?是否还有其他要点用于或更好 _.forEach
lodash不好评价,我没用过。但以下是一些可能有帮助的背景。
'For of' 是在 TypeScript 1.5 中引入的,用于循环每个元素,例如一个数组列表。如果您检查 JS 输出(并且取决于您的目标是 ECMA Script 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++) {
}
根据你的测试,我添加了另一个,使用原生 Array.prototype.forEach
:
list.forEach(function(item) {
console.log("" + item)
});
事实上,这是我的首选方式,因为它实际上更容易打字。它也更接近你可能想用数组做的其他事情,例如map
/filter
等等
请注意 http://jsperf.com/foreach-vs-forof/9 所有这三个都没有似是而非的性能差异。
除了阅读之外,我对打字稿没有任何经验,但我对 ES6/ES2015 有相当多的经验。 for of
过去是,现在仍然是最终确定的 ES2015 规范的一部分。我会阅读 MDN 上关于 for of
的 this 文章。
以下是 for of
和 forEach
的一些相同点和不同点(这些只是我目前发现和知道的):
lodash 中的 forEach
适用于数组、对象或
字符串.
原生 forEach
适用于数组、映射和集合。
for of
适用于所有 Iterables:数组、字符串、TypedArray、
地图、集合、DOM 集合和生成器。
我会在 for of
上阅读 Exploring ES6 的这一章(Exploring ES6 是一本很棒的读物。它非常透彻。它也可以在线免费获得。)其中一些让我印象深刻的地方for of
与 forEach
中不存在的不同。
break and continue work inside for-of loops
break
和 continue
未在 forEach
中公开。在 forEach 中最接近 continue
的是使用 return
,这实际上几乎是同一件事。至于 break
,虽然我看不到其他选择(但不要打折 lodash,因为大多数需要休息的事情,比如查找和返回单个项目,已经包含在 lodash 的大部分库中)。
还应注意 async/await 中的 await
关键字在 for of
中可用,其中 forEach 使得阻止周围块等待 Promises 变得相当困难在 forEach 块中等待,但是可以使用 forEach
,尽管使用 map
或 reduce
可能使等待比 forEach 简单得多(取决于您对这些功能的熟悉程度)。下面是三个单独的等待承诺的实现,分别使用 for of
、forEach
、reduce
和 map
来查看可能的差异。
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.entries
与 forEach
一起使用:
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
的第一个参数默认为您在 for
或 for 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
和使用 let
或 foreach
之间的区别。不同之处在于 var 的变量是 hoisted 到函数范围的顶部(如果它不在函数中则为文件),然后为整个范围重新分配值,因此循环到达终点并最后一次分配 item
,然后每个 settimeout
函数记录最后一次 item
。而使用 let
和 foreach
变量 item
不会被覆盖,因为 item
的作用域是块(当使用 let 时)或函数(当使用 foreach 时) ).
在 forEach
和 for of
之间,您只需要决定哪一个最适合当前的工作(例如,您需要 break
还是需要使用 Maps、Sets 或 Generators使用 for of
)。除此之外,我觉得没有特别充分的理由支持它们都以其核心功能运行的集合。此外,在处理可以使用 forEach
或 for of
的集合时,这主要取决于个人喜好,因为它们以大致相同的速度做同样的事情(并且速度可能随时根据翻译)。我觉得 lodash 的特殊优势在于它的其他各种功能,这些功能实际上可以为您节省大量时间,无需您自己编写代码,例如 map、reduce、filter 和 find。因为你觉得写起来最舒服 for of
我建议你继续这样写,但是一旦你开始使用 lodash 的其他功能来写,你可能会开始觉得用 lodash 的方式写起来更舒服。
编辑:
查看您的代码,我发现您的列表创建存在错误。最后你只有 .split()
而你应该有 .split(",")
。您正在创建整个字符串的长度为 1 的列表,并在该字符串上迭代一次,这就是基准如此相似的原因。我重新运行 测试。 Here 他们是。我仍然不会担心每次 运行.
时它的性能似乎都会发生很大变化
我刚开始在一个使用 TypeScript 的新项目中工作。我来自另一个也使用 TypeScript 的项目。由于 TypeScript 中的原生 for of 循环是可用的,我们决定(旧项目团队)使用这个。特别是对我来说,编写 for of 循环要方便得多,这与我的 java 背景有关。
现在在新项目中,他们到处都使用 _.foreach() 循环来遍历数组。
我想知道的是,of 和 _.foreach()
的原生打字稿之间是否存在性能差异我在 jsperf 中创建了一个小测试,它们接缝的速度或多或少完全相同...
https://jsperf.com/foreach-vs-forof/12
用于
的 TypeScriptfor (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" for of of,因为它对我来说更具可读性。
你们建议使用什么?是否还有其他要点用于或更好 _.forEach
lodash不好评价,我没用过。但以下是一些可能有帮助的背景。
'For of' 是在 TypeScript 1.5 中引入的,用于循环每个元素,例如一个数组列表。如果您检查 JS 输出(并且取决于您的目标是 ECMA Script 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++) {
}
根据你的测试,我添加了另一个,使用原生 Array.prototype.forEach
:
list.forEach(function(item) {
console.log("" + item)
});
事实上,这是我的首选方式,因为它实际上更容易打字。它也更接近你可能想用数组做的其他事情,例如map
/filter
等等
请注意 http://jsperf.com/foreach-vs-forof/9 所有这三个都没有似是而非的性能差异。
除了阅读之外,我对打字稿没有任何经验,但我对 ES6/ES2015 有相当多的经验。 for of
过去是,现在仍然是最终确定的 ES2015 规范的一部分。我会阅读 MDN 上关于 for of
的 this 文章。
以下是 for of
和 forEach
的一些相同点和不同点(这些只是我目前发现和知道的):
-
lodash 中的
forEach
适用于数组、对象或 字符串.原生
forEach
适用于数组、映射和集合。for of
适用于所有 Iterables:数组、字符串、TypedArray、 地图、集合、DOM 集合和生成器。
我会在 for of
上阅读 Exploring ES6 的这一章(Exploring ES6 是一本很棒的读物。它非常透彻。它也可以在线免费获得。)其中一些让我印象深刻的地方for of
与 forEach
中不存在的不同。
break and continue work inside for-of loops
break
和 continue
未在 forEach
中公开。在 forEach 中最接近 continue
的是使用 return
,这实际上几乎是同一件事。至于 break
,虽然我看不到其他选择(但不要打折 lodash,因为大多数需要休息的事情,比如查找和返回单个项目,已经包含在 lodash 的大部分库中)。
还应注意 async/await 中的 await
关键字在 for of
中可用,其中 forEach 使得阻止周围块等待 Promises 变得相当困难在 forEach 块中等待,但是可以使用 forEach
,尽管使用 map
或 reduce
可能使等待比 forEach 简单得多(取决于您对这些功能的熟悉程度)。下面是三个单独的等待承诺的实现,分别使用 for of
、forEach
、reduce
和 map
来查看可能的差异。
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.entries
与 forEach
一起使用:
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
的第一个参数默认为您在 for
或 for 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
和使用 let
或 foreach
之间的区别。不同之处在于 var 的变量是 hoisted 到函数范围的顶部(如果它不在函数中则为文件),然后为整个范围重新分配值,因此循环到达终点并最后一次分配 item
,然后每个 settimeout
函数记录最后一次 item
。而使用 let
和 foreach
变量 item
不会被覆盖,因为 item
的作用域是块(当使用 let 时)或函数(当使用 foreach 时) ).
在 forEach
和 for of
之间,您只需要决定哪一个最适合当前的工作(例如,您需要 break
还是需要使用 Maps、Sets 或 Generators使用 for of
)。除此之外,我觉得没有特别充分的理由支持它们都以其核心功能运行的集合。此外,在处理可以使用 forEach
或 for of
的集合时,这主要取决于个人喜好,因为它们以大致相同的速度做同样的事情(并且速度可能随时根据翻译)。我觉得 lodash 的特殊优势在于它的其他各种功能,这些功能实际上可以为您节省大量时间,无需您自己编写代码,例如 map、reduce、filter 和 find。因为你觉得写起来最舒服 for of
我建议你继续这样写,但是一旦你开始使用 lodash 的其他功能来写,你可能会开始觉得用 lodash 的方式写起来更舒服。
编辑:
查看您的代码,我发现您的列表创建存在错误。最后你只有 .split()
而你应该有 .split(",")
。您正在创建整个字符串的长度为 1 的列表,并在该字符串上迭代一次,这就是基准如此相似的原因。我重新运行 测试。 Here 他们是。我仍然不会担心每次 运行.