为啥扩展元素不适合复制多维数组?

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] ]

【讨论】:

以上是关于为啥扩展元素不适合复制多维数组?的主要内容,如果未能解决你的问题,请参考以下文章

多维数组如何存储在内存中? [复制]

多维动态数组,为啥不起作用?

为啥 Java 没有真正的多维数组?

为啥多维数组的枚举值不等于自身?

为啥我们既有交错数组又有多维数组?

Matlab的多维数组操作