4.59M
Category: educationeducation

Интенсив-курс по JS. Занятие 3

1.

Интенсив-курс
по JS
astondevs.ru

2.

Занятие 3
1. Объекты
Pantone 539 C
2. Функции
CMYK (100/79/43/40)
RGB (0/47/79)
#002F4F

3.

Вернемся к типам данных
Типы данных в JavaScript делятся на две категории:
1. Примитивные;
2. Ссылочные.
К примитивам относятся 7 из 8 типов данных, которые отличаются от ссылочных в большей степени тем, что работа с ними
идёт по значению. Восьмым типом данных является объект. И это единственный тип данных, работа с которым идёт по ссылке.
По сути всё, что не примитив в JavaScript, - это объект. К ним относятся как сами объекты, так и массивы, и даже функции. Все
они в конечном итоге “наследуются”(1) от объектов.
(1) В JavaScript под термином наследование понимается механизм прототипного наследования, который хоть и похож, но всё
же отличается от обычной интерпретации наследования.

4.

Что значит «по значению»
Когда мы создаём переменную и задаём ей в качестве значения, к примеру, число 78, то в конечном итоге получаем переменную
с конкретным значением 78. Просто. Как есть. Во всех последующих случаях обращения к этой переменной в коде мы работаем
именно с её значением.
Как видно на примере, изменения в переменной num никак не влияют на значение переменной newNum. Когда во второй строке
мы как бы присваиваем переменную num переменной newNum, то, на самом деле, в качестве значения для переменной newNum
мы просто задаём текущее значение переменной num, т.е. 78.

5.

Что значит «по ссылке»
Итак, в первой строке мы создаём пустой объект. В переменную obj попадает ссылка на этот объект (никак не сам объект!).
Далее при любом обращении к переменной obj в качестве значения мы получаем ссылку на наш объект где-то в памяти. Так во
второй строке по сути мы делаем копию нашей ссылки и складываем её в переменную newObj. Далее в 4 строке мы изменяем
наш объект, и в конце в обоих случаях закономерно получаем одинаковый вывод, т.к. обе переменные хранят в себе ссылки на
один и тот же объект.

6.

Создание объектов в JS
Литеральная форма создания объектов:
Это самый распространённый и лаконичный способ создания объектов. Он же является более предпочтительным в сравнении
с new Object().
** Создавать объекты с помощью встроенных функций-конструкторов по типу Number, String и т.п. не рекомендуется. Вопервых, данный способ более громоздкий и менее предсказуемый. Во- вторых, “под капотом” JavaScript пытается понять,
является ли данная функция встроенной или пользовательской, что добавляет лишних телодвижений на пустом месте.

7.

Создание объектов в JS
Создание объектов с помощью ключевого слова new:
Создание объектов с помощью встроенных методов Object:
1. С помощью Object.create(proto, ?properties), где proto-это прототип для нового
объекта, а properties - объект, описывающий свойства создаваемого объекта в виде
“название: дескриптор”.
2.
С помощью Object.assign(targetObject, ...sourceObjects), где targetObject - это
объект, в который будут копироваться все свойства объектов sourceObjects (при
этом, в случае совпадения свойств, значения будут перезаписываться).

8.

Дескрипторы объектов
1. value — значение свойства
2. writable — если установлен true, то значение свойства можно изменять
3. configurable — если установлен true, то свойство можно перезаписывать
4. enumerable — если установлен true, то свойство будет перечисляться в цикле for .. in и при использовании метода
Object.keys
5. get — функция, которая будет вызвана при запросе к свойству
6. set — функция, которая будет вызвана при записи свойства

9.

Встроенные методы Object
Object.values(object)
Возвращает массив значений собственных перечисляемых свойств объекта;
Object.entries(object)
Возвращает массив собственных перечисляемых свойств объекта в виде пар [ключ, значение].
Object.keys(object)
Возвращает массив, содержащий имена всех собственных перечислимых свойств переданного объекта.
Object.assign()
Создаёт новый объект путём копирования значений всех собственных перечислимых свойств из одного или более исходных объектов в
целевой объект.
Object.create()
Создаёт новый объект с указанными объектом прототипа и свойствами.
Object.defineProperty()
Добавляет к объекту именованное свойство, описываемое переданным дескриптором.
Object.defineProperties()
Добавляет к объекту именованные свойства, описываемые переданными дескрипторами.
Object.freeze()
Замораживает объект: другой код не сможет удалить или изменить никакое свойство.
Object.getOwnPropertyDescriptor()
Возвращает дескриптор свойства для именованного свойства объекта.
Object.getOwnPropertyNames()
Возвращает массив, содержащий имена всех переданных объекту собственных перечисляемых и не перечисляемых свойств.
Object.getOwnPropertySymbols()
Возвращает массив всех символьных свойств, найденных непосредственно в переданом объекте.
Object.getPrototypeOf()
Возвращает прототип указанного объекта.
Object.is()
Определяет, являются ли два значения различимыми (то есть, одинаковыми)
Object.isExtensible()
Определяет, разрешено ли расширение объекта.
Object.isSealed()
Определяет, является ли объект запечатанным (sealed).
Object.preventExtensions()
Предотвращает любое расширение объекта.
Object.seal()
Предотвращает удаление свойств объекта другим кодом.
Object.setPrototypeOf()
Устанавливает прототип (т.е. внутреннее свойство [[Prototype]])

10.

Проблема копирования объектов
Как мы уже знаем, скопировать объект обычным присваиванием у нас не получится. В этом случае мы создадим лишь
копию ссылки.
Чтобы сделать полноценную копию объекта нам нужно создать новый объект и скопировать в него все свойства старого.
Звучит довольно просто, не так ли? Рассмотрим следующий пример:

11.

Правильное копирование объектов
На самом деле Object.assign() и object spread(1) вполне себе хорошие способы копирования объектов (если помнить об
особенностях копирования таким способом). Однако если нам нужна глубокая копия объекта, то простым использованием
Object.assign() или object spread(1) нам, к сожалению, не обойтись.
Вариантов решения проблемы несколько:
1. Первый и в большинстве случаев самый подходящий способ - использование сторонних библиотек (например, lodash);
2. Использование методов JSON.stringify() и JSON.parse() (неоднозначный вариант, т.к. тоже имеет свои подводные камни,
о которых нужно обязательно помнить);
3. Создание своей собственной функции для глубокого копирования;
4. Реализация паттерна проектирования прототип для классов, экземпляры которых нужно копировать.
(1) Литеральная форма объектов также позволяет легко сделать копию при помощи spread- оператора (...):
{ ...objectToCopy }. Эта возможность появилась лишь в спецификации ES7.

12.

Проблема сравнения объектов
Со сравнением объектов дела обстоят похожим образом. Как и в случае с копированием, сравнение объектов обычными
способами с использованием ==, === и даже Object.is() не даст нам должных результатов.
И снова поговорим о ссылках. В первой строке мы создаём объект и складываем ссылку на него в переменную obj. Во
второй строке мы создаём ещё один, хоть и очень похожий, но всё-таки новый объект и складываем ссылку на него в
переменную anotherObj. Таким образом, мы имеем две разные ссылки на разные объекты и в случае попытки сравнения
закономерно получаем false.

13.

Правильное сравнение объектов
Здесь также существует несколько способов. В зависимости от ситуации может пригодиться любой из них.
1. Ручное сравнение-создание функции, которая принимает на вход два объекта одинакового типа и сравнивает их по
конкретным полям;
2. Ручное сравнение на уровне класса - по сути это тоже самое, что описано в первом пункте, но в рамках класса
(например, метод .equals(obj));
3. Поверхностное сравнение - создание функции, которая принимает на вход два объекта и сравнивает их на основе
свойств верхнего уровня;
4. Глубокое сравнение - создание функции, которая принимает на вход два объекта и сравнивает их на основе свойств на
всех доступных уровнях вложенности.

14.

Другие объекты в JS
Объекты в JavaScript играют ключевую роль. Однако, как мы выяснили ранее, не всё в JavaScript - это объект. Также есть и
примитивы. Таким образом, мы можем вывести для себя одно довольно простое правило: если перед вами что-то, отличное
от примитива, то будьте уверены - перед вами объект.
Очень часто в программах на JavaScript мы будем работать с массивами и функциями. Обе эти сущности в конечном итоге
“наследуются” от объектов. Функции мы очень подробно разберём на одной из следующих лекций, а вот на массивах
остановимся прямо сейчас.

15.

Массивы
Массив, как и объект, представляет данные в виде некоторого набора пар вида “ключ: значение”. Однако если в обычных
объектах главный акцент идёт на удобную работу со значениями по их именам (ключам), то в массивах данные
располагаются в виде проиндексированного списка.
С помощью индексов можно очень просто обращаться к значениям массива. Для этого нужно просто указать нужный
индекс в квадратных скобках сразу после имени соответствующего массива (например, fruits[2]).
Как правило, массивы удобнее всего использовать в ситуациях, когда нам нужно хранить какой- то список данных
одинакового типа. Хоть в JavaScript и допускается возможность хранения в массиве значений разных типов, делать так
крайне не рекомендуется!
Резюмируя, можно сказать, что массив - это упорядоченная коллекция данных.

16.

Массивы
Существуют следующие основные способы создания массива:
В 99.99% случаев используется первый вариант создания, т.к. он лаконичнее и ведёт себя более предсказуемо. Кроме
этого, как видно на примере, массив можно создать сразу со значениями.
Также массивы можно создавать на основе каких-либо итерируемый сущностей (о них позже) или с помощью
встроенного метода Array.of(...elements), передавая в него значения через запятую.

17.

Что такое итерируемые сущности
Итерация по сути означает проход (или перебор). Т.е. итерируемые сущности - это такие сущности, по которым
возможно осуществить проход (или перебрать).
В JavaScript понятие итерируемых сущностей появилось в спецификации ES6. С этого момента любая сущность,
корректно реализующая функцию итерации по ключу [Symbol.iterator], является итерируемой. В той же спецификации
появилась новая реализация цикла for, специально предназначенная для таких сущностей: for .. of. Кроме этого, ранее
упомянутые spread-операторы также пришли к нам в ES6. По умолчанию итерируемыми сущностями являются все
строки, массивы и объекты Map, Set.
Для реализации собственного итератора нужно в объекте по ключу [Symbol.iterator] описать специальную функцию. Эта
функция должна возвращать объект с методом next(), с помощью которого и осуществляется непосредственно итерация.
Метод next() также должен возвращать специальный объект, содержащий поле value - текущее значение итерации, и done
- состояние завершённости итерации (если done равно true, то итерация завершается).
Для создания массива из итерируемой сущности нужно просто вызвать встроенный метод Array.from(iterable).

18.

Методы массивов
Array.prototype.pop()
Удаляет последний элемент из массива и возвращает его.
Array.prototype.push()
Добавляет один или более элементов в конец массива и возвращает новую длину массива.
Array.prototype.reverse()
Переворачивает порядок элементов в массиве — первый элемент становится последним, а последний — первым.
Array.prototype.shift()
Удаляет первый элемент из массива и возвращает его.
Array.prototype.sort()
Сортирует элементы массива на месте и возвращает отсортированный массив.
Array.prototype.splice()
Добавляет и/или удаляет элементы из массива.
Array.prototype.unshift()
Добавляет один или более элементов в начало массива и возвращает новую длину массива.
Array.prototype.concat()
Возвращает новый массив, состоящий из данного массива, соединённого с другим массивом и/или значением (списком
массивов/значений).
Array.prototype.includes()
Определяет, содержится ли в массиве указанный элемент, возвращая, соответственно, true или false.
Array.prototype.join()
Объединяет все элементы массива в строку.
Array.prototype.slice()
Извлекает диапазон значений и возвращает его в виде нового массива.
Array.prototype.toString()
Возвращает строковое представление массива и его элементов. Переопределяет метод Object.prototype.toString().
Array.prototype.toLocaleString()
Возвращает локализованное строковое представление массива и его элементов. Переопределяет метод
Object.prototype.toLocaleString().
Array.prototype.indexOf() Возвращает первый (наименьший) индекс элемента внутри массива, равный указанному значению; или -1, если значение не
найдено.
Array.prototype.lastIndexOf() Возвращает последний (наибольший) индекс элемента внутри массива, равный указанному значению; или -1, если значение
не найдено.

19.

Методы массивов
Array.prototype.forEach()
Вызывает функцию для каждого элемента в массиве.
Array.prototype.entries()
Возвращает новый объект итератора массива Array Iterator, содержащий пары ключ / значение для каждого индекса в массиве.
Array.prototype.every()
Возвращает true, если каждый элемент в массиве удовлетворяет условию проверяющей функции.
Array.prototype.some()
Возвращает true, если хотя бы один элемент в массиве удовлетворяет условию проверяющей функции.
Array.prototype.filter()
Создаёт новый массив со всеми элементами этого массива, для которых функция фильтрации возвращает true.
Array.prototype.find()
Возвращает искомое значение в массиве, если элемент в массиве удовлетворяет условию проверяющей функции или undefined, если такое
значение не найдено.
Array.prototype.findIndex()
Возвращает искомый индекс в массиве, если элемент в массиве удовлетворяет условию проверяющей функции или -1, если такое значение не
найдено.
Array.prototype.keys()
Возвращает новый итератор массива, содержащий ключи каждого индекса в массиве.
Array.prototype.map()
Создаёт новый массив с результатами вызова указанной функции на каждом элементе данного массива.
Array.prototype.reduce()
Применяет функцию к аккумулятору и каждому значению массива (слева-направо), сводя его к одному значению.
Array.prototype.reduceRight()
Применяет функцию к аккумулятору и каждому значению массива (справа-налево), сводя его к одному значению.
Array.prototype.values()
Возвращает новый объект итератора массива Array Iterator, содержащий значения для каждого индекса в массиве.
Array.prototype[@@iterator]()
Возвращает новый объект итератора массива Array Iterator, содержащий значения для каждого индекса в массиве.

20.

Что такое итерируемые сущности
В процессе работы нашего приложения мы часто сталкиваемся с объектами, крайне похожими на массивы, но не
являющимися ими на самом деле. Такие объекты называются псевдомассивами.
По сути это обычные объекты, в которых также, как и в массиве, элементы располагаются в упорядоченном виде (под
индексами) и имеется свойство length. В принципе это всё, что есть общего у массивов и псевдомассивов, т.е. всех тех
встроенных методов массива у последних нет.
Естественно, чаще всего, когда мы встречаем псевдомассив, нам бы хотелось работать с ним как с полноценным
массивом. Сделать это можно разными способами, но т.к. все псевдомассивы по умолчанию также являются
итерируемыми сущностями, то проще всего будет просто использовать уже знакомый нам метод
Array.from(pseudoArray).

21.

Преобразование объектов к примитивам
Первое и самое простое правило преобразования объектов к примитивам - любой объект при приведении к логическому
типу будет равняться значению true. Всегда.
В ситуациях, когда мы пытаемся сложить, отнять или произвести какую-то другую(1) математическую операцию над
двумя объектами, происходит преобразование к числу.
Ситуаций, когда идёт явное приведение объекта к строке не так уж много, но тем не менее они есть. Так, к примеру, во
время вызова браузерной функции alert(object) объект, переданный в качестве параметра, автоматически приводится к
строке или же в случаях, когда мы пытаемся использовать объект в качестве ключа для значения другого объекта.

22.

Подсказки проведения
Когда объект «понимает», что ему нужно привестись к числу или строке, он, опираясь на необходимый тип приведения,
пытается вызвать внутренний метод по ключу [Symbol.toPrimitive] и в качестве параметра передаёт этот тип приведения.
Возможные типы:
1. «string» - явное или неявное преобразование к строке;
2. «number» - явное или неявное преобразование к числу;
3. «default» - в случаях, когда оператор “не уверен”, какой тип данных ожидать (например, во время сложения, т.к. оно
может использоваться и для чисел, и для строк; при сравнении с приведением типа (==) и др.
Как правило, приведение в случаях «number» и «default» выдаёт одинаковый результат. Однако если вы сами описываете
логику приведения типов, то, естественно, вольны решать самостоятельно, как объект будет вести себя в этих случаях.

23.

Symbol.toPrimitive
Если вы хотите самостоятельно реализовать логику приведения объекта к примитиву, то это можно сделать, добавив
функцию по ключу [Symbol.toPrimitive]. Данная функция, как говорилось ранее, во время вызова принимает одно из трёх
строковых значений: «string», «number» или «default».
Например:

24.

Функции
Очень часто в процессе разработки приложений мы встречаемся с ситуациями, когда какое-то действие приходится
выполнять многократно. Например, добавление нового комментария на страницу, отображение оповещения или даже
осуществление простой проверки строки на присутствие определённых символов.
Для того, чтобы раз за разом не копировать один и тот же код в JavaScript есть функции. По сути функции являются
основными «строительными блоками» любого программного кода.
Вы уже наверняка сталкивались с некоторыми браузерными или встроенными в JavaScript функциями.
Вот некоторые из них:
1. alert(?text) - вызов модального окна браузера с текстом text;
2. prompt(?message, ?defaultValue) - вызов модального окна браузера с сообщением message и полем для ввода со
значением по умолчанию defaultValue;
3. parseFloat(string) - приведение строкового значения string к числу с плавающей точкой;
4. setTimeout(callback, delayInMs) - вызов функции callback с задержкой в delayInMs миллисекунд.

25.

Объявление функций
Каждая функция имеет следующий перечень составляющих:
1. Имя(обязательно, исключение-анонимные функции);
2. Список аргументов;
3. Тело;
4. Возвращаемое значение(необязательно).
Пример объявления функции:
Здесь function - ключевое слово для создания функций в JavaScript, greetUser - её название, () перечень аргументов (в данном случае пустой), а в фигурных скобках располагается тело (или код)
функции.

26.

Наименование функций
Имя функции является почти «обязательным» элементом. Функции без имени вполне могут существовать. И, более того,
используются довольно часто. Такие функции называются анонимными, но мы вернёмся к ним позже.
Правила наименования функций почти ничем не отличаются от правил наименования переменных, однако имеют одно
значительное смысловое отличие. В большинстве своем функции - это действия, поэтому имена функций зачастую
содержат глаголы. Например: showWarningAlert, fetchUsers, markAsRead, compareCars и т.п.
** Хорошей практикой будет использование максимально «говорящих» (читабельных) имён для функций. Под этим
имеется ввиду то, что при хорошем наименовании функции, вам сразу же должно становиться понятно, что она делает.
Например, понятно ли вам, что делает функция delete(id)? Ну, наверное, что-то удаляет... А если функция названа
deleteUserById(id)? Стало ли вам понятнее? Я думаю, что да.

27.

Возвращаемое значение функции
Помимо выполнения какого-то кода, функции могут ещё и возвращать результат. По умолчанию любая функция в
JavaScript возвращает значение undefined.
Для возвращения результата нужно внутри тела функции использовать ключевое слово return и через пробел указать
возвращаемое значение. Return может быть вызван в любом месте функции, однако после его вызова функция тут же
завершит свою работу, а весь оставшийся код будет проигнорирован. Тем не менее, это вовсе не значит, что return может
быть использован в теле функции лишь один раз.
Кроме этого return может быть вызван и без значения. В таком случае функция также завершит свою работу, а
возвращаемым значением вновь станет undefined.

28.

Возвращаемое значение функции
Возвращаемым значением функции может являться в буквальном смысле всё! Любой примитив, объект и даже другая
функция.
Отдельно можно выделить случаи возникновения ошибок. Да, это не совсем то, что обычно понимается под
возвращаемым значением, но, так или иначе, если во время выполнения функции JavaScript (или вы сами) сгенерирует
ошибку, то функция тут же завершится, а результатом будет как раз та самая ошибка. Это значение можно отловить с
помощью обёртки соответствующей части кода в конструкцию try .. catch.

29.

Типы функций
В JavaScript существует три основных типа функций:
⎯ Объявляемая функция (function declaration);
⎯ Функциональное выражение (function expression);
⎯ Стрелочная функция (arrow function, ES6).
И ещё один специфичный тип:
⎯ Именованное функциональное выражение (named function expression, NFE).
Кроме этого есть ещё некоторые «состояния» функций:
⎯ Анонимная функция (anonymous function);
⎯ Callback-функция (callback function);
⎯ Функция-конструктор (constructor function);
⎯ Функция высшего порядка (higher-order function).

30.

Объявляемые функции (FD)
Объявляемые функции - это функции, которые объявляются самым обычным способом с помощью ключевого слова
function.
Такие функции имеют одну довольно значимую особенность - они «всплывают». Это означает, что такие функции могут
быть использованы (вызваны) раньше, чем фактически объявлены в коде.
** В строгом режиме видимость function declaration ограничивается блоком.

31.

Функциональные выражения (FE)
Функциональные выражения – это способ объявления функций через переменную. Т.е. мы описываем функцию
обычным способом, но вместо задания имени складываем её в переменную.
В таком случае «всплывать» функция уже, естественно, не будет. Обратиться к ней можно будет лишь после
непосредственной инициализации.

32.

Отличия FD / FE
Отличия Declaration от Expression
Синтаксис
Function Expression создаётся, когда выполнение доходит до него, и затем уже может использоваться.
Function Declaration можно использовать во всем скрипте (или блоке кода, если функция объявлена в блоке)
В строгом режиме, когда Function Declaration находится в блоке {...}, функция доступна везде внутри блока. Но не
снаружи него.
5. Применяя в такой же ситуации Expression мы можем вынести переменную вне блока кода, и уже в блок
переопределять её значение.
1.
2.
3.
4.

33.

Именованные функциональные выражения (named
function expressions)
Named Function Expressions (NFE) – крайне редкий и специфичный вариант описания функций. Визуально он выглядит
как слияние function declaration и function expression, однако ведёт себя как function expression и, в добавок к этому, имеет
возможность сослаться на себя же.
Ещё одной особенностью является то, что дополнительное имя функции не может быть использовано за её пределами.

34.

Стрелочные функции (arrow functions)
Стрелочные функции появились в JavaScript в стандарте ES6 и сами по себе существенно отличаются от остальных
типов. Во-первых, краткий и более удобный синтаксис. Во-вторых, отсутствие псевдомассива arguments. В-третьих, нет
своего this (он берётся сверху), отчего стрелочные функции не могут быть использованы как функции-конструкторы. Вчетвёртых, нет super (как и в случае с this, он берётся сверху). В-пятых, если тело стрелочной функции состоит всего из
одного выражения, то фигурные скобки можно опустить, а возвращаемым значением функции будет результат этого
выражения.
** Стрелочные функции крайне удобно использовать в качестве callback-функций или же в случае описания
функциональных выражений. Всё это идёт по большей части от простоты синтаксиса.

35.

Анонимные функции (anonymous functions)
Анонимные функции – это все функции, у которых нет имени. Всё очень просто. Такие функции, как правило,
используются как стрелочные callback-функции. Оно и логично, ведь нам в большинстве таких случаев нужно просто
описать функцию, которая будет использована только здесь и сейчас, а дальше по коду использоваться не будет.

36.

Callback-функции (callback functions)
Callback-функция – это любая функция, передаваемая в качестве аргумента. В интернете можно найти и более
«развёрнутое» описание, но, так или иначе, все они сводятся к одному и тому же.
Такие функции нужны для того, чтобы использовать их внутри других функций. До появления промисов они очень
широко использовались для обработки http-запросов. Сейчас, естественно, эту нишу они потеряли, но несмотря на это
используются они и по сей день довольно часто. В качестве хорошего примера использования можно привести
перебирающие методы массива.

37.

Функции-конструкторы (constructor functions)
Функции-конструкторы – специальные функции, предназначенные для создания (конструирования) новых объектов.
Такие функции, как правило, не предназначены для прямого вызова, ибо толку с этого будет не много (хотя делать это
всё ещё можно). Эти функции нужны для вызова со специальным оператором new.

38.

IIFE (Immediately Invoked Function Expression)
IIFE (Immediately Invoked Function Expression) это JavaScript функция, которая выполняется сразу же после того, как она
была определена.
Это анонимная функция, которая позволяет скрыть контекст выполнения \ инкапсулировать его от наружного кода.

39.

Функции высшего порядка (higher-order functions)
Функции высшего порядка – это функции, которые или принимают другие функции в виде аргументов (работа с callbackфункциями), или возвращает функцию в качестве результата. И вновь в качестве хорошего примера подойдут
перебирающие методы массивов или метод bind, как пример встроенного метода, возвращающего функцию в качестве
результата.
Функции высшего порядка не могут существовать в языках, не поддерживающих механизм объектов (функций) первого
класса. Функция первого класса – это функция, которая может выступать в роли аргумента функции, быть возвращаемым
значением функции и выступать в роли значения для переменной или поля объекта. Пример функции высшего порядка
(«полифилл» bind):

40.

Локальные переменные
Говоря о функциях высшего порядка, никак нельзя обойти стороной тему замыканий в JavaScript. Но перед тем как
перейти к самим замыканиям нужно разобраться с понятиями локальных и глобальных переменных.
Локальные переменные – это переменные, созданные в рамках некоторой ограниченной области видимости. Для
переменных, созданных с помощью let или const такой областью является блок. Для переменных, созданных с помощью
var – функция.
Локальные переменные существуют лишь в рамках своей области видимости. За её пределами обратиться к ним
невозможно (будет сгенерирована ошибка).

41.

Глобальные переменные
Глобальные переменные – это любые переменные, созданные вне какой-либо ограничивающей области видимости.
Также к глобальным переменным можно отнести поля глобального объекта (window для браузеров, global для Node.js и
т.п.).

42.

Замыкания
1) Замыкание – это функция, которая запоминает свои внешние переменные и может получить к ним доступ.
2) Замыкание - это комбинация функции и лексического окружения, в котором эта функция была определена.
В JavaScript почти(1) все функции являются замыканиями.
(1) Исключением является способ создания функций через new Function(). Любая функция созданная таким способом
всегда ссылается на глобальную область видимости.

43.

Лексическое окружение (lexical environment)
Лексическое окружение – это специальный объект, хранящий в себе информацию обо всех локальных переменных и
ссылку на внешнее окружение (внешний lexical environment). Данный объект скрыт и получить к нему прямой доступ
нельзя. Тем не менее в нынешних реалиях можно чётко обозначить его две ключевые составляющие: Environment
Record – объект, хранящий в себе все локальные переменные (и ещё некоторую другую информацию, как, например,
значение this) и outer – ссылку на внешнее лексическое окружение.
По сути все локальные переменные – это переменные скрытого объекта внутри лексического окружения. То есть при
попытке получения доступа к какой-то переменной, первым делом идёт обращение к этому объекту, чтобы достать
значение с таким именем оттуда, а затем (в случае неудачи), идёт обращение по скрытой ссылке outer к объекту
переменных внешнего окружения. Цикл повторяется до тех пор, пока: а) переменная не будет найдена, б) не закончится
цепочка внешних окружений.
** Каждое приложение также имеет глобальное лексическое окружение, выше которого ничего нет (outer ссылается на
null).

44.

Пример замыкания
Для каждого вызова makeCounter() создаётся новое лексическое окружение
функции, со своим собственным count. Так что, получившиеся функции counter –
независимы.
I) Все функции «при рождении» получают скрытое свойство [[Environment]],
которое ссылается на лексическое окружение места, где они были созданы.
В данном случае, makeCounter создан в глобальном лексическом окружении, так
что [[Environment]] содержит ссылку на него.
II) Код продолжает выполняться, объявляется новая глобальная переменная
counter, которой присваивается результат вызова makeCounter
В момент вызова makeCounter() создаётся лексическое окружение, для хранения
его переменных и аргументов. Как и все лексические окружения, оно содержит
две вещи:
1. Environment Record с локальными переменными. В нашем случае count –
единственная локальная переменная (появляющаяся, когда выполняется
строчка с let count).
2. Ссылка на внешнее окружение, которая устанавливается в значение
[[Environment]] функции. В данном случае, [[Environment]] функции
makeCounter ссылается на глобальное лексическое окружение.

45.

Пример замыкания
Теперь у нас есть два лексических окружения: первое – глобальное, второе – для
текущего вызова makeCounter, с внешней ссылкой на глобальный объект.
III) В процессе выполнения makeCounter() создаётся небольшая вложенная
функция. Все функции получают свойство [[Environment]], которое ссылается на
лексическое окружение, в котором они были созданы. То же самое происходит и
с нашей новой маленькой функцией (которая вложенная).
На этом шаге внутренняя функция была создана, но ещё не вызвана. Код внутри
function() { return count++ } не выполняется
IV) Выполнение продолжается, вызов makeCounter() завершается, и результат
(небольшая вложенная функция) присваивается глобальной переменной counter:

46.

Пример замыкания
V) При вызове counter() для этого вызова создаётся новое лексическое
окружение. Оно пустое, так как в самом counter локальных переменных нет. Но
[[Environment]] counter используется, как ссылка на внешнее лексическое
окружение outer, которое даёт доступ к переменным предшествующего вызова
makeCounter, где counter был создан.
Теперь, когда вызов ищет переменную count, он сначала ищет в собственном
лексическом окружении (пустое), а затем в лексическом окружении
предшествующего вызова makeCounter(), где и находит её.
Хотя makeCounter() закончил выполнение некоторое время назад, его
лексическое окружение остаётся в памяти, потому что есть вложенная функция с
[[Environment]], который ссылается на него.
VI) Модификация происходит «на месте». Значение count изменяется конкретно
в том окружении, где оно было найдено.

47.

Домашнее задание
Задание 1 – Создать объект counter всеми возможными способами;
Задание 2 – Скопировать объект counter всеми возможными способами;
Задание 3 – Создать функцию makeCounter всеми описанными и возможными способами;
Бонус Задание 1 – Написать функцию глубокого сравнения двух обьектов:
const obj1 = { here: { is: "on", other: "3" }, object: Z };
const obj2 = { here: { is: "on", other: "2" }, object: Z };
const deepEqual = (obj1, obj2) => {};
Бонус Задание 2 – Развернуть строку в обратном направлении при помощи методов массивов:
function reverseStr(str) {
return …
}
English     Русский Rules