为啥扩展元素不适合复制多维数组?
Posted
技术标签:
【中文标题】为啥扩展元素不适合复制多维数组?【英文标题】:Why is a spread element unsuitable for copying multidimensional arrays?为什么扩展元素不适合复制多维数组? 【发布时间】:2018-10-27 17:47:05 【问题描述】:来自 mdn:Spread Syntax
注意:通常 ES2015 中的扩展运算符在复制数组时会加深一层。因此,它们不适合复制多维数组。这与 Object.assign() 和 Object spread 语法的情况相同。请查看下面的示例以更好地理解。
var a = [[1], [2], [3]];
var b = [...a];
b.shift().shift(); // 1
// Now array b is: [[2], [3]]
以上陈述的意义何在?上面的代码示例与使用 .slice() 方法将 a 中的数组复制到 b 中的工作方式相同。我尝试在此处向数组添加另一个维度:https://repl.it/HKOq/2,但事情仍然按预期工作。
那么为什么扩展语法不适合复制多维数组呢?
如果有任何帮助,我将不胜感激。
编辑:
阅读 estus 和 vol7ron 的答案帮助我弄清楚了问题。基本上,正如 estus 在技术上指出的那样,数组中只有数组,而不是多维数组。
正如 vol7ron 所解释的,仅复制了数组的第一级,因此内存中的对象对于任何进一步的嵌套元素都保持不变。
我也错误地怀疑使用扩展语法的行为应该与切片运算符有任何不同
【问题讨论】:
...
is not an operator!
@FelixKling — 请修改 MDN 文章,该文章对 spread 和 rest 语法都重复了多次错误。 ...
是一个 punctuator,用于 rest 和 spread 语法。
【参考方案1】:
伙计,程序员真的不擅长展示实际显示差异的示例。
var a = [[['a', 'b'], ['c', 'd']], 'e'];
var b = [...a];
b[0][0][0] = 'z';
b[1] = 'x';
console.log('a', a);
console.log('b', b);
这个输出:
a [[["z", "b"], ["c", "d"]], "e"]
b [[["z", "b"], ["c", "d"]], "x"]
注意到有什么可疑的地方吗?两个数组[0][0][0]
的值都已更改。这意味着两个数组中位于[0][0][0]
的对象被引用到同一个对象,而不是副本。然而[1]
的值是不同的,这意味着它确实是一个副本。
浅拷贝表示第一层拷贝,深层次引用。
【讨论】:
很棒的例子!我已经明白这一点,但无论如何在代码中查看仍然很有帮助! 这应该被接受为最佳答案,也值得赏金。【参考方案2】:数组是对象,[...a]
创建a
数组对象的浅 副本。
语言本身没有多维数组 - 数组中还有另一个数组。是否包含数组、普通对象、函数或原语都没有关系。对于基元,它们的值将被复制。否则,将复制对对象的引用。这是什么
Object.assign() 和 Object 扩展运算符也是如此
部分指代。
关于
上面的代码示例与使用 .slice() 方法将 a 中的数组复制到 b 中的工作方式相同
...确实如此。这是编写a.slice()
或[].concat(a)
的一种更简洁的方式。有相当大的例外。 ES6 rest 运算符(以及Array.from(a)
)同样适用于所有可迭代对象,而不仅仅是数组。
对于对象的深副本,ES6 没有提供任何新功能,对象(数组就是)应该手动递归地复制。为了解决所有问题,使用经过验证的第三方辅助函数仍然有意义,例如 Lodash cloneDeep
。
【讨论】:
所以你是说在其他语言中你可以拥有真正的多维数组,它在内存中都是 1 个数组? 老实说,我不记得这种语言,至少将 n 维数组作为单独的语言实体并不是很实用。如果您有其他高级术语的经验,您可能已经知道规则 - 数组复制通常意味着浅复制(除非另有证明)。 顺便说一句,要弄清楚 ES6 是如何工作的,Babel 和 Typescript REPL 是必不可少的。 Babel 输出规范更严格,TS 输出更易阅读;两者都有帮助。【参考方案3】:不为内部数组元素创建新数组(对于多维数组):
// One-dimensional array
var a = [1,2,3];
var b = [...a];
a[0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0] == 1
// got: b[0] == 1
// Multi-dimensional array
var a = [[1], [2], [3]];
var b = [...a];
a[0][0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0][0] == 1
// got: b[0][0] == 'a'
它的工作方式类似于slice()
,因此您必须遍历数组并为每个维度创建新数组。这是一个简单的例子:
// Multi-dimensional array
var a = [[1], [2], [3]];
var b = (function fn(ar)
return ar.map(el=>Array.isArray(el)&&fn(el)||el)
)(a);
a[0][0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0][0] == 1
// got: b[0][0] == 1
【讨论】:
【参考方案4】:所以该示例试图传达的是var b = [...a];
不会展开 a
的内部数组(例如b = [1,2,3]
),而是b
将是@987654325 @。所以b.shift()
删除并返回b
的第一个元素[1]
,然后第二个shift()
只是从返回的数组中删除1
。一言以蔽之,...
仅到达您的 spreaded 数组的一层,例如var b =[...a]
等效于 var b = [a[0], a[1], a[2]]
,而不是示例中的 var b = [ a[0][0], a[1][0], a[2][0] ]
【讨论】:
以上是关于为啥扩展元素不适合复制多维数组?的主要内容,如果未能解决你的问题,请参考以下文章