В рамках функций ES6 метод Array.prototype.fill () позволяет нам добавлять, заменять или генерировать новые элементы в массиве.

Для меня это умная альтернатива старым циклам for, когда дело доходит до заполнения одних и тех же элементов внутри массива.

Например, если вы хотите создать массив из трех одинаковых элементов, вместо того, чтобы создавать пустой массив и вставлять каждый новый элемент в массив с помощью циклов for, этот однострочный элемент сделает свое дело:

const threeFives = Array(3).fill(5)
threeFives
// [5, 5, 5]

Теперь давайте попробуем реализовать метод fill() в этой задаче классического алгоритма спиральной матрицы.

/* Direction:
Write a function that accepts an integer N, and returns a NxN spiral matrix. */
// Examples: 
matrix(2)
// [[1, 2]
// [4, 3]]
matrix(3)
// [[1, 2, 3],
//  [8, 9, 4],
//  [7, 6, 5]]
matrix(4)
// [[1,   2,  3, 4],
//  [12, 13, 14, 5],
//  [11, 16, 15, 6],
//  [10,  9,  8, 7]]

Если вы не работали над этой задачей, я рекомендую вам сначала попробовать, прежде чем читать дальше.

Большинство найденных мной решений начинаются с заполнения пустого массива пустых подмассивов, например этого:

function matrix(n) {
    const results = []
    for (let i = 0; i < n; i++) {
        results.push([])
    }
    let counter = 1
    let startColumn = 0
    let endColumn = n - 1
    let startRow = 0
    let endRow = n - 1
    while (startColumn <= endColumn && startRow <= endRow){
        // top row
        for (let i = startColumn; i <= endColumn; i++) {
            results[startRow][i] = counter
            counter++
        }
        startRow++
        // right column
        for (let i = startRow; i <= endRow; i++) {
            results[i][endColumn] = counter
            counter++
        }
        endColumn--
        // bottom row
        for (let i = endColumn; i >= startColumn; i--) {
            results[endRow][i] = counter
            counter++
        }
        endRow--
        // start column
        for (let i = endRow; i >= startRow; i--) {
            results[i][startColumn] = counter
            counter++
        }
        startColumn++
    }
    return results
}

Как упоминалось ранее, мы можем использовать fill() вместо цикла for в первых трех строках. Итак, вместо:

const results = []
    for (let i = 0; i < n; i++) {
        results.push([])
    }

Мы могли бы использовать fill() так:

const results = Array(n).fill([])

На этом этапе, когда вы регистрируете results в своей консоли, вы можете видеть, что они производят одинаковый вывод.

Круто, так что если мы сохраним ту же логику в остальной части решения, мы увидим тот же результат, не так ли?

Неправильный.

Согласно документации MDN, если первым параметром метода fill() является объект, каждый слот в массиве будет ссылаться на этот объект.

И именно здесь использование fill() может начать испортить вашу красиво созданную матричную функцию.

Как показано в той же документации MDN, если вы передаете объект и изменяете элементы внутри одного объекта, все последующие объекты в массиве будут заполнены одними и теми же элементами.

let arr = Array(3).fill({}) // [{}, {}, {}]
arr[0].hi = "hi"            // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]

Точно так же в матричной функции все подмассивы, которые мы создали с помощью fill(), изменятся, даже если мы хотим изменить только один подмассив.

Здесь я заносил в консоль результаты каждой итерации при производстве основной матрицы. Как вы можете видеть ниже, все подмассивы, созданные с помощью fill([]), продолжают зеркалировать первый подмассив:

using for loop:  [ [ 1 ], [] ]
using fill([]):  [ [ 1 ], [ 1 ] ]
=====
using for loop:  [ [ 1, 2 ], [] ]
using fill([]):  [ [ 1, 2 ], [ 1, 2 ] ]
=====
using for loop:  [ [ 1, 2 ], [ <1 empty item>, 3 ] ]
using fill([]):  [ [ 1, 3 ], [ 1, 3 ] ]
=====
using for loop:  [ [ 1, 2 ], [ 4, 3 ] ]
using fill([]):  [ [ 4, 3 ], [ 4, 3 ] ]
=====

Означает ли это, что мы не должны использовать fill() для создания массива пустых подмассивов?

Вот решение, которое я нашел, которое, похоже, успешно создает заполнители массива без побочных эффектов:

const results = Array(n).fill().map(()=>Array(n).fill());

Вместо того, чтобы заполнять массив массивами, которые являются типами объектов, мы просто заполняем его пустотой. В JavaScript «ничто» - это undefined, который НЕ является объектом, что позволяет нам переназначать новые элементы на каждой итерации, не затрагивая другие подмассивы.

Вы можете увидеть разницу, сравнив все три способа создания массивов и подмассивов:

using for loop:  [ [ 1 ], [] ]
using fill([]):  [ [ 1 ], [ 1 ] ]
using fill().map():  [ [ 1, undefined ], [ undefined, undefined ] ]
=====
using for loop:  [ [ 1, 2 ], [] ]
using fill([]):  [ [ 1, 2 ], [ 1, 2 ] ]
using fill().map():  [ [ 1, 2 ], [ undefined, undefined ] ]
=====
using for loop:  [ [ 1, 2 ], [ <1 empty item>, 3 ] ]
using fill([]):  [ [ 1, 3 ], [ 1, 3 ] ]
using fill().map():  [ [ 1, 2 ], [ undefined, 3 ] ]
=====
using for loop:  [ [ 1, 2 ], [ 4, 3 ] ]
using fill([]):  [ [ 4, 3 ], [ 4, 3 ] ]
using fill().map():  [ [ 1, 2 ], [ 4, 3 ] ]
=====

В следующий раз, когда у вас возникнет искушение использовать метод fill(), обязательно проверьте тип значения, которое вы передаете. Это был тяжелый урок, который я усвоил, почти взломав задачу кода спиральной матрицы со второй попытки .