【问题标题】:Why is this JavaScript map NOT an Infinite Loop?为什么这个 JavaScript 映射不是无限循环?
【发布时间】:2019-10-25 23:55:23
【问题描述】:

我正在学习 JavaScript。我写了这段代码来学习地图功能。但是后来我很困惑为什么这不是连续映射它,因为每个映射序列都有一个新元素被推送到数组中。它不应该在映射时继续推动新元素吗? 为什么map函数只对原来的三个元素运行,对新推送的元素不运行?

我尝试在节点环境中调试它,arr 变量进入了一个闭包。我知道什么是闭包,但我无法理解这里发生了什么。

let array = [1, 2, 3];

array.map((element) => {
  array.push(10);
  console.log(element);
});

我希望输出应该是1,2,3,10,10,10,10,10,10,10,10......10

但实际输出只有1,2,3

【问题讨论】:

  • 你在哪里看到这个结果?当我运行您的代码时,我只看到 1、2、3,正如预期的那样 because of how .map() is specified to behave
  • 在您的原始代码中,您引用了未定义的arr;我以为你的意思是array
  • 迭代时修改数组不会修改map的迭代。根据 Pointy 提到的规范:The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback.
  • 如果需要无穷大,可以使用for循环
  • 我写错了输出。控制台中只有 1 2 3 。但我的问题仍然是一样的。在回调函数中,我正在改变数组以在每次迭代中拥有更多元素。

标签: javascript arrays closures map-function array-push


【解决方案1】:

引用 MDN:

map处理的元素范围是在第一次调用callback之前设置的。在调用 map 开始后附加到数组的元素将不会被回调访问。如果数组的现有元素发生更改,它们传递给回调的值将是地图访问它们时的值。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Description

所以,它的行为是这样的,因为它就是这样设计的。除了其他原因外,它的设计方式也是为了防止无限循环!

map 是函数式编程世界中的一个函数,其中不变性是一个重要原则。根据这一原则,如果您在输入上调用map(其他变量不变),您将始终得到完全相同相同的结果。允许修改输入会破坏不变性。

【讨论】:

  • 范围是如何存储的。我的意思是它是存储在 map 函数的范围内还是全局范围内?
  • @ExpertNoob 它由.map()的实现在内部维护
  • 我还在调试控制台中看到“arr”在“closure”中。你能向我解释一下这是否与这种行为有关吗?
  • 我认为闭包(即箭头函数创建的环境)与此问题中的行为无关。您只需要知道map 将函数应用于自身的每个元素并返回结果。 map 的规范没有说明它是如何做到的,理解规范(即它应该如何表现)可能比内部实现更有用。
【解决方案2】:

因为它不会改变数组 (请参阅Array​.prototype​.map()

相反,它返回一个新数组,其中包含在调用数组中的每个元素上调用提供的函数的结果。

在下面的 sn-p 中,改变 array 的是对 array.push(10); 的三次调用(原始数组中的每个元素一次),而不是 map 函数本身。

let newArray = array.map((element) => {
    array.push(10); // <-- here you mutate the array
    console.log(element);
});

来自上述文档的重要引述(这里的关键点是):

map处理的元素的范围设置在第一个之前 调用回调。附加到数组的元素 在地图调用开始后,回调将不会访问。


在下面的sn-p中你可以看到一个如何正确使用map函数的例子:

let array = [1,2,3];
let newArray = array.map(element => element + 10);  // sum 10 to every element

console.log('original: ', array); // original:  [1,2,3]
console.log('new one: ', newArray) // new one:  [11,12,13]

最后一个想法(也来自文档),将您发布的代码作为参考:

由于map 构建了一个新数组,因此在不使用返回的数组时使用它是一种反模式;请改用forEachfor-of

你不应该使用地图的标志:

  • A) 您没有使用它返回的数组,和/或

  • B) 您没有从回调中返回值。

【讨论】:

  • 对性能有影响吗?使用 map 而不是 forEach 或 for-of。即使我们没有像原始问题中那样分配新创建的数组,它是否会占用内存?
  • 它对性能有影响吗 ...好吧,我不确定,我们必须执行一些基准测试才能测量它,但这里是一个related post即使我们没有分配新创建的数组是否会占用内存(...)? ....好吧,如果您根本不使用该值,它应该是Garbage Collected(以防它在某些时候占用一些内存,在内部由 map 函数),所以,不,它不应该占用内存。
【解决方案3】:

为什么和 MDN 上的完全一样:

var new_array = arr.map(function callback(currentValue[, index[, array]]){}

map处理的元素范围是在第一次调用callback之前设置的。在调用 map 开始后附加到数组的元素将不会被回调访问。

(感谢来自 MDN 的 Joe 在上面的帖子中引用了这段话。)

一旦 map 被调用,它就会将此时的数组作为它的参数;一旦它被传递,那么任何更改都与前一个变量本身无关。

见下文:

let array = [1, 2, 3];

array.map((element) => {
  array.push(10);
  console.log(element);
});

console.log(array)

【讨论】:

  • 我看不出方法签名或不是引用的参数与此行为有什么关系。
猜你喜欢
  • 1970-01-01
  • 2012-05-02
  • 2011-11-15
  • 2012-11-01
  • 2018-03-17
  • 2023-03-28
  • 2021-08-16
  • 2018-01-13
相关资源
最近更新 更多