Повышение уровня вашего JavaScript (часть 1) - версия ES6

После моей предыдущей статьи Написание чистого JavaScript - версия ES6 я решил сделать еще один шаг и обсудить довольно сложные концепции JavaScript ES6, которые оказались полезными в моей карьере.

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

Это первая часть из двух частей, в которых я расскажу о нескольких важных концепциях, которые вы должны освоить, чтобы улучшить свои навыки JavaScript.

1. Что такое «это»?

Если у вас уже есть некоторый опыт работы с другими языками, такими как C ++ или Java, возможно, вы имели дело с ключевым словом this или, возможно, self для Python. Для этих языков this относится к экземпляру текущего объекта при вызове в методе класса.

JavaScript, однако, использует this как-то иначе: ключевое слово this может использоваться вне контекста функции или метода, а именно в глобальном контексте, и его поведение будет отличаться в зависимости от того, какой режим используется.

Глобальный контекст

Глобальный контекст - это просто Глобальный объект, который определяется как объект window в браузере или объект global в Node JS. Независимо от того, используется ли строгий или нестрогий режим, this всегда будет вести себя одинаково в глобальном контексте.

Например, в любом браузере действует следующее:

console.log(this === window) // true
this.foo = "This is a global variable"
console.log(window.foo) // This is a global variable

Функциональный контекст

Внутри функции поведение this определяется способом вызова функции и используемым режимом.

В JavaScript функцию можно вызвать 4 разными способами. В следующих примерах я покажу, как this ведет себя для каждого случая:

я. Вызов простой функции

В «нестрогом» режиме this будет ссылаться на глобальный объект при вызове функции. Однако в «строгом» режиме this будет установлено значение undefined..

ii. Вызов метода

Внутри метода объекта this просто будет ссылаться на содержащий объект:

Но что, если мы сохраним getCountry в другой переменной?

const getCountry = address.getCountry;

Предполагая, что код выполняется в браузере в «нестрогом» режиме, this вернется к window. Таким образом, getCountry() будет оцениваться как window.country, потому что в этом случае родительский объект не указан:

console.log(getCountry()); // undefined

Вот почему мы должны использоватьFunction.prototype.bind. Метод bind, как следует из названия, привяжет новый объект к указанной функции, открывая this объекта.

const getCountry = address.getCountry.bind({ country: "USA" });
console.log(getCountry()); // USA

iii. Вызов конструктора

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

Чтобы предотвратить такое поведение, в функцию-конструктор следует добавить дополнительную обработку:

Подробнее об этом будет рассказано в разделе Прототипы.

iv. Косвенный вызов

Функции JavaScript поддерживают обращение к другим объектам с помощью ключевого слова this при их вызове. Мы можем добиться этого, используя Function.prototype.call или Function.prototype.apply. И call, и apply делают одно и то же. Единственное отличие состоит в том, что call ожидает, что объект и столько аргументов, сколько необходимо, а apply ожидает, что все аргументы, кроме объекта, будут переданы в виде массива.

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

Более подробные примеры по this смотрите в этой статье.

2. Прототипы

Все объекты JavaScript наследуют свойства и методы от прототипа. Прототипы позволяют нам добавлять методы и свойства к нашим объектам и наследовать функции друг от друга.

При использовании ранее представленного вызова конструктора мы заметим, что JavaScript добавит к нашему объекту свойство prototype, которое указывает на исходный конструктор:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Мы можем добавить метод к нашему исходному конструктору, чтобы получить год рождения. После добавления этого метода все объекты, имеющие Person в качестве объекта-прототипа, унаследуют этот метод:

Person.prototype.getYearOfBirth = function () {
  return new Date().getFullYear() - this.age;
};

Используя ту же процедуру, мы можем добавлять или изменять методы и свойства существующих объектов-прототипов JavaScript, таких как Array.prototype или Date.prototype, однако обычно это не рекомендуется.

Я не буду освещать какие-либо дополнительные материалы по прототипам, но вы всегда можете проверить документацию на наличие более сложных концепций.

3. Классы

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

Вот как мы можем реализовать класс, аналогичный нашему предыдущему объекту-прототипу:

Как правило, классы ES6 просто предоставляют более чистый синтаксис для обычных объектов-прототипов, чтобы они напоминали классы на других языках, таких как Java и C ++, без добавления каких-либо функций ООП.

4. Объемы

Область действия в JavaScript определяет, как можно получить доступ к значениям и функциям в коде. По определению, все области в JavaScript можно описать как глобальные или локальные.

Глобальный масштаб

Проще говоря, все, что определено вне всех функций и фигурных скобок {}, принадлежит глобальной области. Переменные, определенные в глобальной области видимости, можно использовать в любом месте кода.

Как правило, вы хотите максимально ограничить количество глобальных переменных, потому что:

  • Использование глобальных переменных может вызвать конфликты имен при масштабировании кода. Обратите внимание, что повторное объявление переменных const или let вызовет ошибку, а повторное объявление var переопределит переменную.
  • В браузерах глобальные переменные являются членами объекта window, который доступен пользователям и любому скрипту, запущенному на странице. Следовательно, использование глобальных значений может вызвать проблемы с безопасностью.
  • JavaScript выполняет поиск переменных, начиная с текущей области и переходя к следующему родительскому объекту, пока не достигнет глобальной области. Наличие большого количества глобальных переменных может привести к проблемам с производительностью.

Локальный охват

Локальная область видимости - это область действия локальных переменных. Локальная область действия состоит из областей функция и блок.

  • Объем функции

Переменные, объявленные внутри функции, доступны только внутри одной функции. Это правило применяется к переменным, объявленным с использованием var, let или const:

  • Область действия блока

Введенные в ES6 переменные, объявленные с использованием const или let в любой фигурной скобке {}, имеют область видимости блока, что означает, что они доступны только внутри одного и того же блока. Область видимости блока фактически является подмножеством области видимости функции - за исключением случая стрелочных функций с неявным возвратом.

5. Подъем

В JavaScript подъем - это стандартный процесс перемещения объявлений переменных и функций в верхнюю часть текущей области перед выполнением кода. Технически это означает, что вы можете использовать переменные до их объявления.

foo = "Variable to declare later";
console.log(foo); // Variable to declare later
var foo;

Важно знать, что вверх перемещаются только объявления, но не инициализации:

console.log(foo); // undefined
var foo = "Variable initialized later";

Здесь объявление foo перемещено наверх, но, поскольку оно инициализируется только позже, оно сначала будет содержать значение undefined.

Блок прицел и подъем

Как обсуждалось ранее, const и let имеют блочную область видимости. Блок «знает» об этих переменных, но не может использовать их до того, как они будут объявлены. Значения, объявленные с помощью const и let, считаются находящимися в «временной мертвой зоне», пока не будут объявлены.

Инициализация let перед объявлением выдаст ReferenceError:

Однако инициализация const перед объявлением вызовет SyntaxError, поэтому код даже не запустится:

Подъем функции

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

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

6. Закрытие

Определение закрытия из спецификации:

Комбинация функции и лексического окружения, в котором эта функция была объявлена.

Другими словами, замыкание - это внутренняя функция, которая может обращаться к переменным своей включающей или внешней функции. Отсюда можно сделать вывод, что у замыкания есть три цепочки областей видимости:

  • Доступ к собственной области блока
  • Доступ к области его включающей функции (или внешней функции)
  • Доступ к глобальной области

Рассмотрим следующий пример:

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

Внутренняя функция по-прежнему будет иметь доступ к значениям из своей внешней функции, даже если внешняя функция уже вернулась.

7. Каррирование

Концепция закрытия в JavaScript позволяет нам извлечь выгоду из каррирования, которое является важной концепцией функционального программирования.

Каррирование - это преобразование функций, которое переводит функцию из вызываемой как f(a, b, c) в вызываемую как f(a)(b)(c)

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

Более реалистичным вариантом использования каррирования будет реализация обработчиков событий с предопределенными аргументами. В качестве примера возьмем следующий тривиальный компонент React:

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

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

8. IIFE

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

Важным вариантом использования будет использование IIFE и Module Pattern JavaScript для реализации объекта Singleton:

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

import MySingletonDAO from "./MyDAO"
const instance1 = MySingletonDAO.getInstance()
const instance2 = MySingletonDAO.getInstance()
console.log(instance1 === instance2) // true

В этой части я кратко обсудил несколько концепций, которые вам необходимо освоить, чтобы улучшить свои навыки JavaScript. В следующей части я сосредоточусь на других важных предметах, таких как асинхронные функции и итераторы. Надеюсь, вам понравилось читать, и до встречи!