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

Объекты

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

Ключи объекта всегда являются строками. Даже если мы попытаемся назначить число в качестве клавиши (например, 5), число будет преобразовано в строку (5 => «5»). Ключи объекта также называются свойствами.

Объекты можно инициализировать фигурными скобками, например: let person = {};. Здесь мы объявляем переменную с именем person и присваиваем ей пустой объект.

Мы получаем доступ и присваиваем новые значения объекту, используя запись через точку или скобки.

Точка:

person.age = 25;
console.log(person.age) // returns 25

Обозначение Квадратные скобки:

person['age'] = 25;
console.log(person['age']) // returns 25

Аналогичным образом мы можем переназначить значение свойства age.

person['age'] = 30;
console.log(person['age']) // returns 30

Что, если мы хотим увидеть все ключи объекта или все значения объекта? Для этого есть несколько способов.

Object.keys(person) возвращает массив ключей в объекте person.

Object.values(person) возвращает массив значений в объекте person.

Для повторения каждого ключа мы можем использовать цикл for. В приведенном ниже фрагменте кода я использую цикл for...in, который перебирает ключи объекта. Внутри цикла ключ записывается (с помощью интерполяции строк) в первой строке. В следующей строке доступ к значению, связанному с этим ключом, осуществляется через скобки и заносится в консоль.

for (let key in person) {
  console.log(`Key: ${key}`);
  console.log(`Value: ${person[key]}`);
}

Когда мы запускаем этот код, мы получаем вывод ниже:

Key: name
Value: Bob
Key: age
Value: 25
Key: eyeColor
Value: brown

Переменные как указатели

Одно важное отличие состоит в том, что объекты ведут себя не так, как примитивные типы данных. Переменная, назначенная объекту, не просто хранит копию объекта, как это было бы для числа 5. Скорее, объект хранится где-то еще в памяти, и переменная указывает на этот адрес в памяти. Если мы попытаемся сделать копию переменной, новая переменная просто укажет на тот же объект в памяти. Таким образом, любые изменения, внесенные в копию, также будут внесены в оригинал. Давайте посмотрим на пример:

let person = {name: 'Jessica', age: 25, eyeColor: 'brown'};
let copyOfPerson = person; 
copyOfPerson.name = 'Bob'; 
console.log(person.name); // person.name is now 'Bob'

Чтобы объяснить приведенный выше фрагмент кода, мы сначала инициализируем литерал объекта с именем person, содержащий свойство name, свойство age и свойство eyeColor. В строке 2 мы инициализируем новую переменную с именем copyOfPerson и присваиваем ей переменную person. В строке 3 мы переназначаем значение свойства name переменной copied на «Боб». Изменит ли это исходный объект person?

В строке 4 мы записываем значение свойства name объекта person. Он регистрирует «Боб»! Это означает, что оригинал был изменен, даже когда мы изменили только копию, демонстрируя, что обе переменные указывают на один и тот же объект в памяти.

Вот почему мы должны быть очень осторожны с изменениями, которые мы вносим в объекты, чтобы избежать непреднамеренных ошибок. Мы можем использовать метод Object.freeze(), чтобы предотвратить изменение свойств объектов (однако этот метод работает только на один уровень в глубину).

Массивы

Массивы представляют собой упорядоченный список элементов, к которым можно получить доступ по индексу. Массивы также являются объектами. Поскольку они являются объектами, они также изменяемы. Это означает, что мы можем добавлять, удалять и переназначать значения в массивах так же, как и для объектов. Как и в случае с объектами, когда переменной присваивается массив, переменная действует как указатель на массив в памяти. Вот пример, показывающий, что функция может изменять глобальную переменную:

let a = [1, 2, 3, 4, 5];
function removeElement(arr) {
  arr.pop();
  return arr;
}
removeElement(a);
console.log(a); 
// [1, 2, 3, 4]

Сначала мы объявляем массив, присвоенный переменной a. Мы также определяем функцию, которая имеет один параметр arr (массив) и выполняет метод pop для локальной переменной arr, содержащей ссылку. Мы вызываем функцию removeElement, передавая a (которая является ссылкой на [1, 2, 3, 4, 5]) в качестве аргумента. Если бы мы передали примитивный тип данных, такой как строка или число, в качестве аргумента, то была бы передана копия самого значения, поэтому функция не могла бы изменить значение глобальной переменной. После выполнения оператора console.log(a) мы видим, что a действительно был изменен функцией removeElement.

Теперь мы объявляем константную переменную с именем grades, содержащую массив из шести целых чисел. Постоянные переменные не могут быть назначены, но можно ли изменить этот массив?

const grades = [100, 90, 80, 70, 60, 50];
grades.pop();
console.log(grades); 
// [100, 90, 80, 70, 60]

Используя метод pop(), мы пытаемся удалить последний элемент из массива. Интересно, что он успешно удаляет элемент, несмотря на то, что мы объявили оценки с const. Это потому, что мы изменяем только элементы внутри массива, что позволяет const, не указывая на новое место. Если бы мы попытались переназначить константную переменную grades совершенно новому массиву, это бы не сработало. *Обратите внимание, что приведенный выше фрагмент кода приведен в иллюстративных целях — подобное изменение константных переменных НЕ рекомендуется.

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

В JavaScript есть большая коллекция методов массивов более высокого порядка, которые чрезвычайно полезны. Здесь я опишу метод forEach и метод some, хотя есть и много других.

Методы массива более высокого порядка

Array.prototype.forEach()

Метод forEach принимает функцию обратного вызова в качестве параметра и выполняет функцию для каждого элемента массива. Функция обратного вызова принимает параметр element, а также необязательные параметры для индекса и массива. Аргумент element представляет текущий элемент массива, поскольку массив повторяется с увеличением индекса. Тело функции выполняет какое-то действие с этим элементом. Функция обратного вызова вызывается один раз для каждого элемента (поэтому она и называется forEach!). После завершения вызовов функции обратного вызова метод forEach возвращает undefined.

Ниже приведен пример использования forEach для чередования двух массивов в новый массив с сохранением их порядка.

let arr1 = [1, 2, 3];
let arr2 = ['a', 'b', 'c'];
let result = [];
arr1.forEach((element, idx) => {
  result.push(element);
  result.push(arr2[idx]);
});
console.log(result) // [1, "a", 2, "b", 3, "c"]

Во-первых, мы объявляем два массива, arr1 и arr2, которые мы хотим объединить в новый массив, который мы инициализировали в переменной result. Затем метод forEach вызывается для arr1. Мы передаем функцию обратного вызова, и внутри этой функции обратного вызова мы предоставляем два аргумента. Параметр index является необязательным, но используется в этом примере. Внутри функции обратного вызова мы push текущий элемент помещаем в массив result, а затем push элемент с текущим индексом arr2 в массив result. Эта функция обратного вызова вызывается 3 раза, так как в arr1 есть 3 элемента. Функция обратного вызова вызывается с элементами массива в следующем порядке:

(element = 1, index = 0) => result.push(1); result.push("a");
(element = 2, index = 1) => result.push(2); result.push("b");
(element = 3, index = 2) => result.push(3); result.push("c");

В заключение отметим, что метод forEach невероятно полезен для самых разных случаев использования массивов.

Array.prototype.some()

Метод some также принимает функцию обратного вызова в качестве параметра и возвращает true, если какой-либо элемент в массиве соответствует условию, предоставленному функцией обратного вызова.

Как работает функция обратного вызова, так это то, что она принимает текущий элемент в качестве аргумента с необязательными аргументами для индекса и массива. Аргумент element представляет текущий элемент массива, поскольку массив повторяется с увеличением индекса. Тело функции проверяет некоторое условие и возвращает значение, которое может быть истинным или ложным. Если возвращаемое значение истинно, то метод some немедленно возвращает true. Если ни один вызов функции обратного вызова не возвращает истинное значение, метод some возвращает false.

Ниже приведен пример, иллюстрирующий использование этого метода в массиве:

let words = ['ant', 'bear', 'bat', 'cat', 'dog'];
let result1 = words.some((element) => element.includes('at'));
console.log(result1); // true
let result2 = words.some((element) => element.includes('z'));
console.log(result2); // false

В приведенном выше фрагменте кода мы сначала объявляем переменную с именем words и присваиваем ей массив строк. Затем мы вызываем метод some для массива words.

В функции обратного вызова первого вызова метода мы проверяем, содержит ли текущий элемент подстроку «at». Поскольку «bat» содержит эту подстроку, функция обратного вызова возвращает true для этого элемента. Затем метод some возвращает true, который выводится на консоль.

В функции обратного вызова второго метода мы проверяем, содержит ли текущий элемент подстроку «z». Функция обратного вызова выполняется для каждой строки в массиве, но возвращает false для каждой. Поскольку ни одно из возвращаемых значений не было истинным, метод secondsome возвращает false.

Есть несколько других замечательных методов работы с массивами, которые я не буду здесь описывать, например map, filter, reduce, find и every, но их все можно найти в веб-документах MDN.

Правдивость

Так что же мы имеем в виду, когда говорим «истинно» и «ложно»? Во-первых, эти прилагательные имеют разные значения от «истинного» и «ложного». true и false — два логических значения. С другой стороны, ложные значения включают false, 0, NaN, "", undefined и null. Все остальные значения истинны, например «собака», 15 и true. Все истинные значения оцениваются какtrue, а все ложные значения оцениваются как false. Однако это не означает, что истинные значения равны true или что ложные значения равны false!

Давайте посмотрим на пример ниже. Мы объявляем переменную с именем word и присваиваем ей строковое значение «false». Условный оператор в строке 2 проверяет истинность переменной word. Если оператор оценивается как истинное значение, выполняется строка 3. В противном случае выполняется оператор else в строке 5.

let word = "false";
if (word) {
  console.log(`${word} is truthy`);
} else {
  console.log(`${word} is falsy`);
}

Это выводит «false is truely». Это связано с тем, что «false» — это строка, а не логическое значение false. Непустые строки всегда правдивы, независимо от того, какое слово находится внутри строки. Итак, в строке 2, когда условный оператор оценивает истинность переменной word, word является истинным, поэтому оператор оценивается как true. Таким образом, строка 3 — это оператор, который выполняется.

Давайте рассмотрим другой пример: мы объявляем переменную с именем number и присваиваем ей значение 0. Затем мы проверяем условие number === false, которое оценивается как 0 === false. Если условие возвращает истинное значение, выполняется строка 3. Если нет, выполняется оператор else в строке 5.

Как вы думаете, что следующий фрагмент кода выведет после условного оператора в строке 2?

let number = 0;
if (number === false) {
  console.log(`${number} is equal to false`);
} else {
  console.log(`${number} is not equal to false`);
}

Приведенный выше код выводит «0 не равно false» (из строки 5). Это означает, что условный оператор в строке 2 оценивается как false. Хотя 0 равно falsy, строго не равно false. Однако, если бы мы вместо этого использовали оператор ==, тогда выражение оценивалось бы как true из-за приведения типов.

Как мы видели, важно различать, ищем ли мы только истинное/ложное значение или логические значения true/false, поскольку они могут привести к разным результатам.

Это все на данный момент. Спасибо за чтение!