Акт 1: «Ядро» — Часть 5 (Неизменяемость и массивы)

Это продолжение серии о JavaScript, начатой ​​здесь.

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

Неизменяемые и операции на месте:

Важным понятием, которое нужно знать, является неизменность. Как мы знаем, переменные относятся к данным в памяти. Если вы можете изменить эти данные в памяти через переменную, то она изменчива, иначе неизменяема. Например, строки неизменяемы:

var a = '';
a = a + 'x';  // creates a new object in memory

Массивы изменяются с помощью так называемых операций на месте:

var a = [];
a.push('x');
console.log(a); // ['x']

Это показывает, что метод push изменял объект в памяти.

Не все операторы над массивами изменяемы. Например:

var a = [];
var b = a.concat(['x']);
console.log(a); // []
console.log(b); // ['x']

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

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

Некоторые менее известные методы Array:

Массивами можно манипулировать различными способами. Важно знать, работает ли метод на месте или неизменен (см. предыдущий раздел).

Метод unshift добавляет новый элемент в начало на месте:

var a = [];
a.unshift(1);
console.log(a); // [1]

Метод shift удаляет первый элемент на месте:

var a = [1, 2];
a.shift(1);
console.log(a); [2]

Array.isArray — это официальный метод проверки того, является ли объект массивом. Напомним, что typeof [] вернет только 'object':

Array.isArray([]); // true

Самый простой способ очистить массив — просто установить его length в 0:

var a = [1, 2];
a.length = 0;
console.log(a); // []

Метод from позволяет создать новый массив из заданного итерируемого объекта. Что такое итерации, будет рассмотрено позже, а пока достаточно знать, что сами массивы являются итерируемыми:

var a = [1, 2];
var b = Array.from(a);
console.log(b); // [1, 2]

Аналогичным вышеприведенному методу является of, который создает массив из заданного списка параметров:

var a = Array.of(1, 2, 3);
console.log(a); // [ 1, 2, 3 ]

Быстрый способ создать массив с предварительно заполненными значениями — использовать конструктор Array и метод fill:

var a = Array(3); // constructs an array of length 3
a.fill(0);
console.log(a); // [0,0,0]

Массивы в JavaScript по умолчанию разреженные. То есть вы можете назначать определенные индексы, не назначая никаких других:

var a = [];
a[1000] = 1;
a[2000] = 2;
console.log(a); // [ <1000 empty items>, 1, <999 empty items>, 2 ]

Вместо удаления элементов можно воспользоваться методом splice:

var a = [0, 1, 2, 3];
a.splice(1, 2);
console.log(a); // [0,3]

Этот метод удаляет 2 элементов, начиная с индекса 1. Итак, первый параметр указывает индекс начала удаления, а второй количество удаляемых элементов.

Основные функциональные концепции массивов:

some , filter , every , find

Все эти методы схожи в своем использовании. Они ожидают предикат, которому передается каждый элемент массива. some и find вырываются наружу, когда предикат выполняется некоторым элементом:

var a = [1, 2, 3];
console.log(a.some(e => e === 2)); // true
console.log(a.every(e => e > 0));  // true
console.log(a.filter(e => e > 1)); // [2,3]
console.log(a.find(e => e === 3)); // 3

reduce , forEach , map

reduce и map работают с данными и возвращают новый массив, тогда как forEach просто перебирает массив и позволяет выполнять побочные эффекты с помощью заданной функции:

[1, 2, 3].reduce((prev, e) => fn(prev, e), init);

reduce перебирает массив и вызывает для каждого элемента заданную функцию fn. В качестве первого параметра принимает возвращаемое значение предыдущего вызова fn или, если предыдущего вызова не было, использует заданный параметр init. В качестве второго параметра он передает текущий итерируемый элемент.

Это хорошее упражнение — попытаться реализовать этот метод самостоятельно или, по крайней мере, попытаться понять следующий код:

function reduce(a, f, next, idx = 0) {
  return idx < a.length ?
   reduce(a, f, f(next, a[idx]), ++idx) :  
   next;
}
console.log(
  reduce([1, 2, 3], (prev, e) => ({ ...prev, [e]: e }), {})  
); // { '1': 1, '2': 2, '3': 3 }
// this is equivalent to:
console.log([1, 2, 3].reduce((prev, e) => ({ ...prev, [e]: e }), {}));  // { '1': 1, '2': 2, '3': 3 }

Эта функция рекурсивно вызывает себя и каждый раз предоставляет в качестве параметра возвращаемое значение вызова f. Сам f получает текущее значение next и элемент массива a, соответствующий текущему индексу итерации. Это дает f на каждой итерации возможность изменять значение next в зависимости от текущего элемента массива.

Метод flat рекурсивно сглаживает все элементы. Это,

var a = [1, 2, [3, 4, [5, 6]]];
console.log(a.flat()); // [ 1, 2, 3, 4, [ 5, 6 ] ]

Вы можете управлять параметром, насколько глубоким должно быть это сглаживание:

var a = [1, 2, [3, 4, [5, 6]]];
console.log(a.flat(2)); // [ 1, 2, 3, 4, 5, 6 ]

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

Это хорошее упражнение для самостоятельной реализации такого метода:

function flat(a) {
  return a.reduce((p, e) => {
    if (!Array.isArray(e)) {
      p.push(e);
    } else {
      p.push(...flat(e));
    }
    return p;
  }, []);
}
var a = [1, 2, [3, 4, [5, 6]]];
console.log(flat(a)); // [ 1, 2, 3, 4, [ 5, 6 ] ]

В этом примере показано хорошее применение функции reduce. Хотя для начинающих этот стиль кода и, в частности, рекурсивные вызовы могут показаться довольно сложными, вам следует ознакомиться с этим как можно раньше. Часто используется на практике.

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

Спасибо за чтение!