【问题标题】:JavaScript - why Array.prototype.fill actually fills a "pointer" of object when filling anything like 'new Object()'JavaScript - 为什么 Array.prototype.fill 在填充“new Object()”之类的东西时实际上填充了对象的“指针”
【发布时间】:2018-11-18 13:20:37
【问题描述】:

我试图使用 Array.prototype.fill 方法来创建 anxn 2D 数组,但代码有问题,我最终发现里面的所有数组实际上都是指向相同的 “指针”数组

示例:

var matrix = new Array(10).fill(new Array(10), 0);

我认为从概念上讲这可以创建一个 10 x 10 2D 数组。但是,如果我为 matrix[0][0] 赋值:

matrix[0][0] = 1;

结果实际上是:

matrix[0][0] === 1;
matrix[1][0] === 1;
matrix[2][0] === 1;
matrix[3][0] === 1;
matrix[4][0] === 1;
matrix[5][0] === 1;
matrix[6][0] === 1;
matrix[7][0] === 1;
matrix[8][0] === 1;
matrix[9][0] === 1;

每次如果我尝试为矩阵中的任何位置赋值,其他子数组中的所有对应位置也会发生变化。

我想知道为什么会这样?

谁能回答这个令人头疼的问题?

【问题讨论】:

标签: javascript arrays


【解决方案1】:

我想知道为什么会这样?

Array#fill 将您提供的 value 作为第一个参数,并用该 value 的副本填充数组。

你给它的值是一个数组的引用,所以它给你的自然是一个填充了该引用副本的数组。不是 array 的副本,reference 的副本。

例如,它的行为方式与此代码完全相同:

var a = new Array(10);
var b = a;

...给我们留下ab 都引用同一个数组(都包含相同的;对我们创建的单个数组的引用)。

让我们向它扔一些 Unicode 艺术:

这段代码运行后:

var a = new Array(10);
var b = a;

我们有这个记忆(减去一些不相关的细节):

a:Ref89895−−−+ | | +−−−−−−−−−−−−−−−−−+ +−−−−−−>|数组 | | +−−−−−−−−−−−−−−−−−+ | |长度:10 | b:Ref89895−−−+ +−−−−−−−−−−−−−−−−+

ab 包含一个引用,我在这里将其显示为 Ref89895,尽管我们从未看到实际值。这是b = a 复制的内容,而不是数组本身。

同样,当你这样做时:

var matrix = new Array(10).fill(new Array(10), 0);

你最终得到了

+−−−−−−−−−−−−−−−−−+ 矩阵:Ref89895−−−>|数组 | +−−−−−−−−−−−−−−−−−+ |长度:10 | | 0: Ref55462 |--\ | 1: Ref55462 |--\\ | 2: Ref55462 |--\\\ | 3: Ref55462 |--\\\\ +−−−−−−−−−−−−−−−−+ | 4: Ref55462 |---+++++->|数组 | | 5: Ref55462 |--///// +−−−−−−−−−−−−−−−−+ | 6: Ref55462 |--//// |长度:10 | | 7: Ref55462 |--/// +−−−−−−−−−−−−−−−−+ | 8: Ref55462 |--// | 9: Ref55462 |--/ +−−−−−−−−−−−−−−−−−+

要创建一个 10 位数组,其中 10 个位置中的每一个本身都是 0 的 10 位数组,我可能会使用 Array.fromfillmap

// Array.from
var matrix = Array.from({length: 10}, function() {
    return new Array(10).fill(0);
});

// fill and map
var matrix = new Array(10).fill().map(function() {
    return new Array(10).fill(0);
});

或在 ES2015 中:

// Array.from
let matrix = Array.from({length: 10}, () => new Array(10).fill(0));

// fill and map
let matrix = new Array(10).fill().map(() => new Array(10).fill(0));

【讨论】:

  • @Soviut:是的,很好,而且更干净。顺便说一句,您错过了一个,我将其添加到您的答案中。
  • 这太棒了!非常感谢您为绘制图表所做的努力,这对您有很大帮助!我认为这个错误的发生是因为我实际上更熟悉 C++ 而不是很熟悉 JavaScript。
  • @AndyRightNow:很高兴有帮助! (是的,C++ 确实有一些 JavaScript 没有的自动对象复制语义,所以根据我们假设的 Array#fill 在 C++ 中的编写方式,它可能只复制引用,也可能复制对象。)
【解决方案2】:

这是因为当您调用 array.fill() 时,您只传递了一个 new Array() 值。这就是重点,用单个值填充数组。

如果您想获取现有数组并为每个索引分配一个新实例,您可以使用.map()

var matrix = new Array(10).fill().map(function(item, index, arr) {
    return new Array(10);
});

要以您似乎想要的嵌套方式执行此操作,您可以映射两次。

// the callback function's item, index and array properties are optional
var matrix = new Array(10).fill().map(function(item, index, arr) {
    return new Array(10).fill(0);
});

如果您使用的是 ES6,这可以进一步简化为单行。

var matrix = new Array(10).fill().map(() => new Array(10).fill(0));

.map() 起作用的原因是它为数组中的每个项目调用了提供的回调函数。

.fill().map() 之前出现空白的原因是用值填充数组。默认情况下,给定大小的新数组的每个项目都有undefined.fill() 将临时填充数组,以便对其进行映射。

【讨论】:

    【解决方案3】:

    在您的特定情况下,由于 Array.prototype.fill() 填充单个值并且在 JS 对象(例如数组)中是引用类型,因此您的所有索引都带有对第一个索引的引用。在 JS 中创建 ND 数组可能不像看起来那么简单,因为数组将通过引用复制。对于通用arrayND 函数,您需要一个数组克隆实用程序函数。让我们看看;

    Array.prototype.clone = function(){
      return this.map(e => Array.isArray(e) ? e.clone() : e);
    };
    function arrayND(...n){
      return n.reduceRight((p,c) => c = (new Array(c)).fill().map(e => Array.isArray(p) ? p.clone() : p ));
    }
    var array3D = arrayND(5,5,5,"five");
    array3D[0][1][2] = 5;
    console.log(array3D);

    arrayND 函数接受不定数量的参数。最后一个指定填充我们的 ND 数组的默认值,前面的参数是它们代表的每个维度的大小。 (第一个参数是维度 1,第二个参数是维度 2 等等......)

    【讨论】:

    • 是的,我完全同意这很棘手(或者只是我对它不够熟悉)。感谢您的精彩回答!
    【解决方案4】:

    通过递归,您可以制作更多维度;)

    const range = r => Array(r).fill().map((v, i) => i);
    const range2d = (x, y) => range(x).map(i => range(y));
    const rangeMatrix = (...ranges) => (function ranger(ranged) {
      return ranges.length ? ranger(range(ranges.pop()).map(i => ranged)) : ranged
    })(range(ranges.pop()));
    
    let arr10x10 = range2d(10, 10);
    let arr3x5 = range2d(3, 2);
    let arr4x3 = rangeMatrix(4, 3);
    let arr4x3x2 = rangeMatrix(4, 3, 2);
    let arr4x3x2x5 = rangeMatrix(4, 3, 2, 5);
    
    console.log(arr10x10);
    console.log(arr3x5);
    console.log(arr4x3);
    console.log(arr4x3x2);
    console.log(arr4x3x2x5);

    【讨论】:

      猜你喜欢
      • 2020-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-13
      • 2023-01-26
      • 1970-01-01
      • 2010-10-17
      相关资源
      最近更新 更多