【问题标题】:Array filter changes the main array数组过滤器改变主数组
【发布时间】:2016-12-13 09:43:28
【问题描述】:

我注意到 node.js 的数组过滤器中有一些奇怪的行为。 有一个简单的数组和一个循环:

var array = [
{
    name:"bob",
    planet:"earth"
},
{
    name:"mike",
    planet:"mars"
},
{
    name:"vlad",
    planet:"jupiter"
}];

var filtered = array.filter(function(x){
    return x.name !== "mike";
});

console.log(array); //lets print how normal array looks like
console.log("---");
console.log(filtered); //lets print how filtered one looks like

for(var i = 0; i < filtered.length; i++)
{
    delete filtered[i].planet; //remove planet
    filtered[i].name = filtered[i].name + "[NEW]"; //add NEW to the name
}

console.log("After replacement:");
console.log(array);//lets print how normal array looks like now
console.log("-----------");
console.log(filtered);//lets print how filtered array looks like now

理论上,array 数组不应该更改,因为我没有以任何方式操作它。然而,这就是我在控制台中得到的:

[ { name: 'bob', planet: 'earth' },
  { name: 'mike', planet: 'mars' },
  { name: 'vlad', planet: 'jupiter' } ] //this array is normal
---
[ { name: 'bob', planet: 'earth' },
  { name: 'vlad', planet: 'jupiter' } ] //this is good behavior, since I don't need "mike"

After replacement:

[ { name: 'bob[NEW]' },
  { name: 'mike', planet: 'mars' },
  { name: 'vlad[NEW]' } ] //this should not be changed in any way
-----------
[ { name: 'bob[NEW]' }, { name: 'vlad[NEW]' } ] //this is correct

为什么会这样?我需要array 保持与开始时相同。

谢谢。

【问题讨论】:

  • 是常见的JS范式,googlemutable Js objects
  • 如果您不想改变两个数组中引用的对象,您似乎已经准备好使用immutable.js

标签: javascript arrays node.js


【解决方案1】:

你的代码在这里:

for(var i = 0; i < filtered.length; i++)
{
    delete filtered[i].planet; //remove planet
    filtered[i].name = filtered[i].name + "[NEW]"; //add NEW to the name
}

...不改变 either 数组。它正在改变两个数组引用的对象的状态。

更简单的例子:

var a = [{answer:null}];
var b = a.filter(function() { return true; }); // Just a copy, but using `filter` for emphasis
a[0].answer = 42;
console.log(b[0].answer); // 42

这一行:

a[0].answer = 42;

不会改变ab,它会改变a[0] 所指的状态,b[0] 指的。

让我们扔一些 ASCII-art Unicode-art 给它:

在这一行之后:

var a = [{answer:null}];

这是我们记忆中的内容(忽略一些不相关的细节);

+−−−−−−−−−−−−−−−+ |变量“a” | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−+ | Ref11542 |−−−−>|数组 | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+ | 0: Ref88464 |−−−−>|对象 | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−+ |答案:空 | +−−−−−−−−−−−−−−−+

a 引用一个数组对象,它有一个0 属性,它引用了一个有answer 属性的对象。我使用“Ref11542”和“Ref88494”来表示aa[0] 包含的对象引用,但当然我们从未真正看到这些引用的值;它们是 JavaScript 引擎私有的。

然后我们这样做:

var b = a.filter(function() { return true; }); // Just a copy, but using `filter` for emphasis

现在我们有了:

+−−−−−−−−−−−−−−−+ |变量“a” | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−+ | Ref11542 |−−−−>|数组 | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−+ | 0: Ref88464 |−−+ +−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ +−>|对象 | |变量“b” | | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ | |答案:空 | | Ref66854 |−−−−>|数组 | | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ | | 0: Ref88464 |−−+ +−−−−−−−−−−−−−−−+

请注意,两个数组都包含相同的对象引用(此处显示为“Ref88464”);它们指向相同的对象。

现在我们这样做:

a[0].answer = 42;

所做的只是改变对象的状态;它对ab 或它们引用的数组没有影响:

+−−−−−−−−−−−−−−−+ |变量“a” | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−+ | Ref11542 |−−−−>|数组 | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−+ | 0: Ref88464 |−−+ +−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ +−>|对象 | |变量“b” | | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ | |答案:42 | | Ref66854 |−−−−>|数组 | | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ | ^ | 0: Ref88464 |−−+ +−−−−−−−− 这里只有变化 +−−−−−−−−−−−−−−−+

自然而然

console.log(b[0].answer);

...输出 42。


在您提出的评论中:

但是那我如何设置过滤对象的状态而不是主要的呢?

请记住,objects 在两个数组中是相同的。您还没有复制对象,您只是创建了一个新数组,其中只有其中一些对象。

如果您希望能够在不影响第一个数组中的对象的情况下更改这些对象的属性,则需要制作对象的副本

如果对象只包含简单的原始值,那很容易;一般情况下是相当困难的。 :-)

但是,在您的情况下,由于您的对象只有 nameplanet 属性,接下来您要做的就是删除 planet 属性并更改名称,我们可以轻松创建对象;见 cmets:

var array = [
{
    name:"bob",
    planet:"earth"
},
{
    name:"mike",
    planet:"mars"
},
{
    name:"vlad",
    planet:"jupiter"
}];

var filtered = array.filter(function(x){
    return x.name !== "mike";
}).map(function(x) {
    // Create a *new* object, setting its `name` to the `name`
    // of the original object plus [NEW], and ignoring its
    // `planet` property entirely
    return {name: x.name + "[NEW]"};
});

console.log(array);
console.log("---");
console.log(filtered);

或者,您可能只想通过数组:

var filtered = [];
array.forEach(function(x){
    if (x.name !== "mike") {
        filtered.push({name: x.name + "[NEW]"});
    }
});

【讨论】:

  • 那么我该如何设置过滤对象的状态而不是主要的呢?
  • 复制到一个变量然后赋值给一个[0]
  • 感谢您的回答如此直截了当且易于理解。
  • @Nedas:我在答案末尾添加了一条注释。关键是复制对象,这样它们就不再被共享了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-22
  • 1970-01-01
  • 1970-01-01
  • 2020-04-06
相关资源
最近更新 更多