Fill()使用对象传递引用而不是新实例

我玩了一会儿,试图实例化一个长度为 x的新数组,在这个数组中,所有元素都被初始化为值 y:

var arr = new Array(x).fill(y);

如果 y的值不是 一个物体,那么这个函数就可以很好地工作。 这意味着如果 y是一个对象,那么以下内容是正确的:

var arr = new Array(2).fill({});
arr[0] === arr[1]; //is true;
arr[0].test = 'string';
arr[1].test === 'string'; //is also true;

有没有什么方法可以在使用 fill-function 时为每个元素创建一个新对象?或者我应该把它转换成一个循环?

25608 次浏览

You can first fill the array with any value (e.g. undefined), and then you will be able to use map:

var arr = new Array(2).fill().map(u => ({}));
var arr = new Array(2).fill().map(Object);

The accepted answer is good and would work in 90% of cases.

But if you are making high-performance JS application, and if you work with big/huge arrays, Array.map(..) creates big overload in both - memory and processor use, as it creates a copy of an array.

I recommend to use the classic for loop:

    a = new Array(ARRAY_SIZE);
for (var i = 0; i < ARRAY_SIZE; i++) {
a[i] = [];
}
// or it's one line alternative
for (var i = 0, a = []; i < ARRAY_SIZE; a[i++] = []);

I tested six alternatives and got this:

  • Array.map(), as proposed above (11x times!!! slower):

     a = new Array(ARRAY_SIZE).fill().map(u => { return []; });
    
  • for loop, the best one (fastest):

     // Standard multi-line way
    a = new Array(ARRAY_SIZE);
    for (var i = 0; i < ARRAY_SIZE; i++) {
    a[i] = [];
    }
    
    
    // One line syntax
    for (var i = 0, a = []; i < ARRAY_SIZE; a[i++] = []);
    
  • forEach (6x time slower):

     a = new Array(ARRAY_SIZE).fill();
    a.forEach((val, i) => {
    a[i] = [];
    })
    

[UPDATE 2020-08-27] One more way proposed by Ilias Karim below

  • Array.from (30x times!!! slower) - apparently worse in terms of performance, despite the nicest syntax :(

     a = Array.from({ length: ARRAY_SIZE }, () => []);
    
  • [..Array(..)] (5x times!!! slower)

     a = [...Array(ARRAY_SIZE)].map(_=>([]))
    
  • Array.push(..), second place in terms of performance (2x times!!! slower)

     let a = [], total = ARRAY_SIZE;
    while(total--) a.push([]);
    

PS. I used this fiddle for tests.

One performant solution: Array.from({ length: 5 }, () => new Object())

Ilias Karim's answer is most excellent. I just did the following:

a = Array.from({length:l}, () => new Array(c).fill(prefix));

to create a pre-filled 2D array of the specified size, l by c, filled with prefix. Now my code can fill in the slots in the 2D matrix that need non-prefix values.

Shortest Possable:

let node =  [...Array(2)].map(_=>({}))
console.log(node)

I wrote a blog post about this: http://www.samhenderson.xyz/posts/12

But the TLDR is that if you want to avoid chaining multiple function e.g. fill, map. And want to avoid writing a loop, then you can use:

const array = Array.from({ length: 2 },()=>({}))

For an array of arrays:

const array = Array.from({ length: 2 },()=>([]))

To add to answers that explain the aliasing issue and how to solve it, here's a handy function that can be used to create arrays with cleaner syntax for the caller:

const array = (length, fill) =>
[...Array(length)].map((_, i) =>
typeof fill === "function" ? fill(i) : fill
);




// usage:
const a = array(3, i => array(3, j => [i, j]));
a[0][0][0] = -42;
console.log(a);

Note that you still need to use a callback function for non-primitive values. This is actually a feature as it exposes the index and lets you provide arbitrary logic to fill the element. If you're concerned about accidentally passing a non-primitive, non-function object as the fill value, you can throw an error.

If you really want to be able to pass an object directly and have it copied under the hood, here's an adjustment that pretty much prohibits aliasing:

const array = (length, fill) =>
[...Array(length)].map((x, i) =>
typeof fill === "function" ? fill(i) :
typeof fill === "object" ? _.cloneDeep(fill) : fill
);




// usage:
const a = array(2, array(2, {foo: 3}));
a[0][0].foo = 42;
console.log(a);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

In general, I suggest avoiding .fill() almost entirely in favor of the spread syntax [...Array()] because it's easy to forget about the aliasing behavior and wind up with a frustrating bug.

If speed matters, use a traditional for loop:

const array = (length, fill) => {
const a = [];


for (let i = 0; i < length; i++) {
a[i] = typeof fill === "function" ? fill(i) : fill;
}


return a;
};


// usage:
const a = array(2, () => array(2, Object));
a[0][0].foo = 42;
console.log(a);