Переменные
JavaScript-приложению обычно нужно работать с информацией. Например:
- Интернет-магазин – информация может включать продаваемые товары и корзину покупок.
- Чат – информация может включать пользователей, сообщения и многое другое.
Переменные используются для хранения этой информации.
Переменная
Переменная – это «именованное хранилище» для данных. Мы можем использовать переменные для хранения товаров, посетителей и других данных.
Для создания переменной в JavaScript используйте ключевое слово let .
Приведённая ниже инструкция создаёт (другими словами: объявляет или определяет) переменную с именем «message»:
Теперь можно поместить в неё данные, используя оператор присваивания = :
Строка сохраняется в области памяти, связанной с переменной. Мы можем получить к ней доступ, используя имя переменной:
Для краткости можно совместить объявление переменной и запись данных в одну строку:
Мы также можем объявить несколько переменных в одной строке:
Такой способ может показаться короче, но мы не рекомендуем его. Для лучшей читаемости объявляйте каждую переменную на новой строке.
Многострочный вариант немного длиннее, но легче для чтения:
Некоторые люди также определяют несколько переменных в таком вот многострочном стиле:
…Или даже с запятой в начале строки:
В принципе, все эти варианты работают одинаково. Так что это вопрос личного вкуса и эстетики.
В старых скриптах вы также можете найти другое ключевое слово: var вместо let :
Ключевое слово var – почти то же самое, что и let . Оно объявляет переменную, но немного по-другому, «устаревшим» способом.
Есть тонкие различия между let и var , но они пока не имеют для нас значения. Мы подробно рассмотрим их в главе Устаревшее ключевое слово "var".
Аналогия из жизни
Мы легко поймём концепцию «переменной», если представим её в виде «коробки» для данных с уникальным названием на ней.
Например, переменную message можно представить как коробку с названием "message" и значением "Hello!" внутри:
Мы можем положить любое значение в коробку.
Мы также можем изменить его столько раз, сколько захотим:
При изменении значения старые данные удаляются из переменной:
Мы также можем объявить две переменные и скопировать данные из одной в другую.
Переменная может быть объявлена только один раз.
Повторное объявление той же переменной является ошибкой:
Поэтому следует объявлять переменную только один раз и затем использовать её уже без let .
Примечательно, что существуют функциональные языки программирования, такие как Scala или Erlang, которые запрещают изменять значение переменной.
В таких языках однажды сохранённое «в коробку» значение остаётся там навсегда. Если нам нужно сохранить что-то другое, язык заставляет нас создать новую коробку (объявить новую переменную). Мы не можем использовать старую переменную.
Хотя на первый взгляд это может показаться немного странным, эти языки вполне подходят для серьёзной разработки. Более того, есть такая область, как параллельные вычисления, где это ограничение даёт определённые преимущества. Изучение такого языка (даже если вы не планируете использовать его в ближайшее время) рекомендуется для расширения кругозора.
Имена переменных
В JavaScript есть два ограничения, касающиеся имён переменных:
- Имя переменной должно содержать только буквы, цифры или символы $ и _ .
- Первый символ не должен быть цифрой.
Примеры допустимых имён:
Если имя содержит несколько слов, обычно используется верблюжья нотация, то есть, слова следуют одно за другим, где каждое следующее слово начинается с заглавной буквы: myVeryLongName .
Самое интересное – знак доллара ‘$’ и подчёркивание ‘_’ также можно использовать в названиях. Это обычные символы, как и буквы, без какого-либо особого значения.
Эти имена являются допустимыми:
Примеры неправильных имён переменных:
Переменные с именами apple и APPLE – это две разные переменные.
Можно использовать любой язык, включая кириллицу или даже иероглифы, например:
Технически здесь нет ошибки, такие имена разрешены, но есть международная традиция использовать английский язык в именах переменных. Даже если мы пишем небольшой скрипт, у него может быть долгая жизнь впереди. Людям из других стран, возможно, придётся прочесть его не один раз.
Существует список зарезервированных слов, которые нельзя использовать в качестве имён переменных, потому что они используются самим языком.
Например: let , class , return и function зарезервированы.
Приведённый ниже код даёт синтаксическую ошибку:
Обычно нам нужно определить переменную перед её использованием. Но в старые времена было технически возможно создать переменную простым присвоением значения без использования let . Это все ещё работает, если мы не включаем use strict в наших файлах, чтобы обеспечить совместимость со старыми скриптами.
Это плохая практика, которая приводит к ошибке в строгом режиме:
Константы
Чтобы объявить константную, то есть, неизменяемую переменную, используйте const вместо let :
Переменные, объявленные с помощью const , называются «константами». Их нельзя изменить. Попытка сделать это приведёт к ошибке:
Если программист уверен, что переменная никогда не будет меняться, он может гарантировать это и наглядно донести до каждого, объявив её через const .
Константы в верхнем регистре
Широко распространена практика использования констант в качестве псевдонимов для трудно запоминаемых значений, которые известны до начала исполнения скрипта.
Названия таких констант пишутся с использованием заглавных букв и подчёркивания.
Например, сделаем константы для различных цветов в «шестнадцатеричном формате»:
- COLOR_ORANGE гораздо легче запомнить, чем "#FF7F00" .
- Гораздо легче допустить ошибку при вводе "#FF7F00" , чем при вводе COLOR_ORANGE .
- При чтении кода COLOR_ORANGE намного понятнее, чем #FF7F00 .
Когда мы должны использовать для констант заглавные буквы, а когда называть их нормально? Давайте разберёмся и с этим.
Название «константа» просто означает, что значение переменной никогда не меняется. Но есть константы, которые известны до выполнения (например, шестнадцатеричное значение для красного цвета), а есть константы, которые вычисляются во время выполнения сценария, но не изменяются после их первоначального назначения.
Значение pageLoadTime неизвестно до загрузки страницы, поэтому её имя записано обычными, а не прописными буквами. Но это всё ещё константа, потому что она не изменяется после назначения.
Другими словами, константы с именами, записанными заглавными буквами, используются только как псевдонимы для «жёстко закодированных» значений.
Придумывайте правильные имена
В разговоре о переменных необходимо упомянуть, что есть ещё одна чрезвычайно важная вещь.
Название переменной должно иметь ясный и понятный смысл, говорить о том, какие данные в ней хранятся.
Именование переменных – это один из самых важных и сложных навыков в программировании. Быстрый взгляд на имена переменных может показать, какой код был написан новичком, а какой – опытным разработчиком.
В реальном проекте большая часть времени тратится на изменение и расширение существующей кодовой базы, а не на написание чего-то совершенно нового с нуля. Когда мы возвращаемся к коду после какого-то промежутка времени, гораздо легче найти информацию, которая хорошо размечена. Или, другими словами, когда переменные имеют хорошие имена.
Пожалуйста, потратьте время на обдумывание правильного имени переменной перед её объявлением. Делайте так, и будете вознаграждены.
Несколько хороших правил:
- Используйте легко читаемые имена, такие как userName или shoppingCart .
- Избегайте использования аббревиатур или коротких имён, таких как a , b , c , за исключением тех случаев, когда вы точно знаете, что так нужно.
- Делайте имена максимально описательными и лаконичными. Примеры плохих имён: data и value . Такие имена ничего не говорят. Их можно использовать только в том случае, если из контекста кода очевидно, какие данные хранит переменная.
- Договоритесь с вашей командой об используемых терминах. Если посетитель сайта называется «user», тогда мы должны называть связанные с ним переменные currentUser или newUser , а не, к примеру, currentVisitor или newManInTown .
Звучит просто? Действительно, это так, но на практике для создания описательных и кратких имён переменных зачастую требуется подумать. Действуйте.
И последняя заметка. Есть ленивые программисты, которые вместо объявления новых переменных повторно используют существующие.
В результате их переменные похожи на коробки, в которые люди бросают разные предметы, не меняя на них этикетки. Что сейчас находится внутри коробки? Кто знает? Нам необходимо подойти поближе и проверить.
Такие программисты немного экономят на объявлении переменных, но теряют в десять раз больше при отладке.
Дополнительная переменная – это добро, а не зло.
Современные JavaScript-минификаторы и браузеры оптимизируют код достаточно хорошо, поэтому он не создаёт проблем с производительностью. Использование разных переменных для разных значений может даже помочь движку оптимизировать ваш код.
Итого
Мы можем объявить переменные для хранения данных с помощью ключевых слов var , let или const .
- let – это современный способ объявления.
- var – это устаревший способ объявления. Обычно мы вообще не используем его, но мы рассмотрим тонкие отличия от let в главе Устаревшее ключевое слово "var" на случай, если это всё-таки вам понадобится.
- const – похоже на let , но значение переменной не может изменяться.
Переменные должны быть названы таким образом, чтобы мы могли легко понять, что у них внутри.
Задачи
Работа с переменными
- Объявите две переменные: admin и name .
- Запишите строку "Джон" в переменную name .
- Скопируйте значение из переменной name в admin .
- Выведите на экран значение admin , используя функцию alert (должна показать «Джон»).
В коде ниже каждая строка решения соответствует одному элементу в списке задач.
Памятка по современному JavaScript
Cheatsheet for the JavaScript knowledge you will frequently encounter in modern projects.
Памятка по современному JavaScript
За картинку спасибо Ahmad Awais ⚡️
Введение
Мотивация
В этом документе собраны возможности языка JavaScript, с которыми вы наверняка столкнетесь в современных проектах и примерах кода.
Цель этого руководства — не обучить вас JavaScript с нуля, а помочь разработчикам с базовыми знаниями, которые при изучении современных кодовых баз (или, скажем, React) сталкиваются со сложностями из-за использованных в них концепций JavaScript.
Иногда я буду давать личные советы, которые могут быть спорными, но постараюсь упоминать, что это личное мнение.
Примечание: Большинство представленных здесь понятий взяты из обновления языка JavaScript (ES2015, которое часто называют ES6). Вы можете найти новые возможности из этого обновления здесь; они хорошо описаны.
Дополнительные ресурсы
Если вам сложно разобраться с каким-то понятием, рекомендую искать ответы на вопросы на следующих ресурсах:
-
. . . — бесплатный курс от Udacity. . для поиска специализированных блогов и ресурсов. .
Содержание
Понятия
Объявление переменных: var , const , let
В JavaScript есть три ключевых слова, отвечающих за объявление переменных, и у каждого из них свои особенности. Эти слова − var , let и const .
Краткое объяснение
Переменным, объявленным с помощью ключевого слова const , нельзя позже присвоить новое значение, в то время как переменным, объявленным с помощью let или var , можно.
Я рекомендую всегда объявлять переменные ключевым словом const , а let использовать только в том случае, если позже эту переменную понадобится изменить или переопределить.
| Область видимости | Можно переопределять | Можно изменять | Временная мертвая зона | |
|---|---|---|---|---|
| const | Блок | Нет | Да | Да |
| let | Блок | Да | Да | Да |
| var | Функция | Да | Да | Нет |
Пример кода
Подробное объяснение
Область видимости переменной определяет, где эта переменная доступна в коде.
Областью видимости переменных, объявленных с помощью var , является функция. Это означает, что если переменная была создана внутри функции, то у всего внутри этой функции есть доступ к данной переменной. Кроме того, переменная с областью видимости внутри функции недоступна за пределами этой функции.
Можно думать об этом вот так: если у переменной область видимости Х, то эта переменная — как бы свойство Х.
Вот менее очевидный пример области видимости переменных:
Кроме этого, переменные, объявленные с помощью ключевого слова var , при выполнении кода перемещаются в начало области видимости. Это называется поднятие переменных.
Этот фрагмент кода:
при выполнении понимается как:
var и let примерно одинаковы, в то время как переменные, объявленные словом let :
- имеют в качестве области видимости блок;
- недоступны до объявления;
- не могут быть повторно объявлены в той же области видимости.
Давайте разберемся, в чем особенности блочной области видимости, используя предыдущий пример:
Теперь разберемся, что значит «переменные, объявленные с помощью let и const , недоступны до их объявления»:
В отличие от переменных, объявленных через var , попытка обратиться к переменной let или const до её объявления вызовет ошибку. Этот феномен часто называют Временной мёртвой зоной.
Примечание: строго говоря, объявления переменных с использованием let и const тоже поднимаются, однако их инициализация — нет. Они сделаны так, что использовать их до инициализации нельзя. Поэтому интуитивно кажется, что такие переменные не поднимаются, но на самом деле это не так. Больше информации можно найти в этом очень подробном объяснении.
В дополнение к сказанному: нельзя повторно объявить переменную, объявленную с помощью let :
const
Переменные, объявленные через const , ведут себя так же, как переменные, объявленные через let , но к тому же их нельзя переопределять.
Итак, переменные, объявленные с помощью const :
- имеют в качестве области видимости блок;
- недоступны до объявления;
- не могут быть повторно объявлены в той же области видимости;
- не могут быть переопределены.
Но есть одна тонкость: переменные, объявленные с помощью const , не являются неизменными! А именно, это означает, что объекты и массивы, объявленные с помощью const , могут быть изменены.
В случае объектов:
В случае массивов:
Дополнительные материалы
Стрелочные функции
В обновлении JavaScript ES6 добавлены стрелочные функции — новый синтаксис записи функций. Вот некоторые их преимущества:
- краткость;
- this берется из окружающего контекста;
- неявный возврат.
Пример кода
- Краткость и неявный возврат.
Внутри стрелочной функции значение this такое же, как и во внешней области видимости. В принципе, со стрелочными функциями вам больше не нужно прибегать к хаку that = this перед вызовом функции внутри функции.
Подробное объяснение
Краткость
Стрелочные функции во многих отношениях более краткие, чем обычные. Рассмотрим все возможные случаи:
- Явный и неявный возврат.
Функция может явно возвращать результат с использованием ключевого слова return .
При обычном способе написания функций возврат всегда был явным. Со стрелочными функциями его можно сделать неявным. Это значит, что для возврата значения не нужно использовать ключевое слово return .
Поскольку здесь нет ничего, кроме возвращаемого значения, можно вернуть значение без явного указания.
Для этого нам просто нужно убрать фигурные скобки и ключевое слово return . Поэтому это и называется неявным возвратом: ключевого слова return нет, но функция все равно вернет x * 2 .
Примечание: Если ваша функция не возвращает никакого значения (с побочными эффектами), то в ней нет ни явного, ни неявного возврата.
Кроме того, если вы хотите неявно вернуть объект, вы должны заключить его в круглые скобки, так как иначе он будет конфликтовать с фигурными скобками блоков:
- Только один аргумент.
Если ваша функция принимает только один аргумент, то скобки вокруг него можно опустить. Возвращаясь к функции double в коде выше:
Скобки вокруг этого аргумента можно опустить:
- Без аргументов.
Когда стрелочная функция вообще не принимает никаких аргументов, нужно использовать пустые круглые скобки, иначе синтаксис будет неправильным.
Использование this
Чтобы понять эту тонкость поведения стрелочных функций, нужно понимать, как this ведёт себя в JavaScript.
Внутри стрелочной функции значение this равно значению this внешнего окружения. Это значит, что стрелочная функция не создает новый this , а получает его из окружения.
Без использования стрелочных функций для получения доступа к переменной через this в функции, вложенной в другую функцию, придется использовать хак that = this или self = this .
Вот, к примеру, использование функции setTimeout внутри функции myFunc :
Но в случае стрелочных функций this берется из окружения:
Полезные ресурсы
Значение аргументов функции по умолчанию
Начиная с обновления JavaScript ES2015, аргументам функции можно присваивать значения по умолчанию, используя следующий синтаксис:
Значения по умолчанию применяются только в двух случаях:
- значение не передано;
- передано значение undefined .
Другими словами, если передать в функцию параметр null , то параметр по умолчанию не применится.
Примечание: Присваивать значение по умолчанию можно в том числе и при работе с деструктурированными параметрами (см. пример в следующем понятии).
Дополнительные материалы
Деструктуризация объектов и массивов
Деструктуризация — это удобный способ создания новых переменных путем извлечения значений из объектов или массивов.
На практике деструктуризацию можно использовать, чтобы присваивать переменным разбитые на части параметры функции или this.props в React-проектах.
Объяснение с помощью примера кода
- Объект.
Давайте использовать во всех примерах следующий объект:
С деструктуризацией всё поместится в одну строку:
Примечание: В const < age >= person; скобки после ключевого слова const используются не для обозначения объекта или блока. Это синтаксис деструктуризации.
- Параметры функции.
Деструктуризация часто используется для разбиения параметров функции на части.
Если деструктурировать параметр person , то функция получится куда более лаконичной:
Ещё удобнее использовать деструктуризацию со стрелочными функциями:
- Массив.
Давайте рассмотрим следующий массив:
С использованием деструктуризации:
Полезные ресурсы
Методы массивов — map / filter / reduce
map , filter и reduce — это методы массивов, пришедшие из парадигмы функционального программирования.
- Array.prototype.map() принимает массив, каким-нибудь образом преобразует его элементы и возвращает новый массив трансформированных элементов.
- Array.prototype.filter() принимает массив, просматривает каждый элемент и решает, убрать его или оставить. Возвращает массив оставшихся значений.
- Array.prototype.reduce() принимает массив и вычисляет на основе его элементов какое-то единое значение, которое и возвращает.
Я рекомендую пользоваться ими как можно чаще, следуя принципам функционального программирования, потому что они лаконичные, элегантные и их можно комбинировать.
Вооружившись этими тремя методами, вы можете обойтись без использования for и forEach в большинстве ситуаций. Когда в следующий раз соберётесь запустить цикл for , попробуйте решить задачу с помощью map , filter и reduce . Поначалу это будет трудно, потому что вам придётся научиться мыслить по-другому, но, разобравшись один раз, вы сможете применять эти методы без особых усилий.
Пример кода
Давайте посчитаем сумму баллов всех студентов, которые набрали больше 10 баллов, используя map , filter и reduce :
Объяснение
Давайте использовать в качестве примера следующий массив:
Array.prototype.map()
Что же здесь происходит? Мы применяем к массиву numbers метод map , который взаимодействует с каждым элементом массива, передавая его в нашу функцию. Цель функции — произвести расчёт и вернуть новое значение, чтобы map мог подставить его вместо переданного в функцию.
Давайте даже вынесем функцию из массива, чтобы было понятнее, что происходит:
numbers.map(doubleN) создаёт [doubleN(0), doubleN(1), doubleN(2), doubleN(3), doubleN(4), doubleN(5), doubleN(6)] , что равняется [0, 2, 4, 6, 8, 10, 12] .
Примечание: Если вам не нужно возвращать новый массив и вы просто хотите перебрать существующий массив, совершая с его элементами некоторые действия, можете просто использовать for / forEach вместо метода map .
Array.prototype.filter()
Мы применяем filter к массиву numbers . Метод filter взаимодействует с каждым элементом массива и передаёт его в нашу функцию. Функция возвращает булево значение, определяющее, будет ли элемент сохранён в массиве. Затем filter возвращает массив отфильтрованных значений.
Array.prototype.reduce()
Цель метода reduce заключается в том, чтобы вычислить на основе массива какое-то одно значение. Какие именно вычисления метод произведет с элементами, зависит только от вас.
Так же, как методы .map и .filter , метод .reduce применяется к массиву и в качестве первого параметра принимает функцию.
На этот раз, впрочем, кое-что изменилось:
- .reduce принимает два параметра.
Первый параметр — это функция, которая будет вызываться на каждом шаге цикла.
Второй параметр — это значение аккумулирующей переменной ( acc в нашем случае) на первом шаге цикла (чтобы разобраться, читайте далее).
- Параметры функции.
Функция, которую вы передаёте в качестве первого параметра метода .reduce , принимает два аргумента. Первый аргумент — это аккумулирующая переменная ( acc в нашем примере), второй аргумент — текущий элемент.
Аккумулирующая переменная равна значению, возвращённому нашей функцией на предыдущем шаге цикла. В самом начале каждого цикла acc равна значению, которое было передано в качестве второго параметра .reduce .
На первом шаге
acc = 0 , потому что мы передали 0 в качестве второго параметра метода reduce .
n = 0 — первый элемент массива number .
Функция возвращает acc + n –> 0 + 0 –> 0.
На втором шаге
acc = 0 , потому что это значение функция вернула на предыдущем шаге.
n = 1 — второй элемент массива number .
Функция возвращает acc + n –> 0 + 1 –> 1.
На третьем шаге
acc = 1 , потому что это значение функция вернула на предыдущем шаге.
n = 2 — третий элемент массива number .
Функция возвращает acc + n –> 1 + 2 –> 3.
На четвертом шаге
acc = 3 , потому что это значение функция вернула на предыдущем шаге.
n = 3 — четвёртый элемент массива number .
Функция возвращает acc + n –> 3 + 3 –> 6.
На последнем шаге
acc = 15 , потому что это значение функция вернула на предыдущем шаге.
n = 6 — последний элемент массива number .
Функция возвращает acc + n –> 15 + 6 –> 21.
Поскольку это был последний шаг, .reduce возвращает 21 .
Дополнительные материалы
Оператор расширения .
Оператор расширения . , появившийся в ES2015, предназначен для развертывания итерируемых объектов (например, массивов) в тех местах, где можно поместить несколько элементов.
Пример кода
Объяснение
В итерируемых объектах (например, массивах)
Если у нас есть два следующих массива:
Первый элемент массива arr2 — это массив, потому что arr1 напрямую вставляется в arr2 . Но мы хотим, чтобы arr2 состоял только из букв. Чтобы добиться этого, мы можем развернуть элементы массива arr1 в массиве arr2 .
С использованием оператора расширения:
Оставшиеся аргументы функции
Для объединения аргументов можно использовать оператор оставшихся аргументов функции. Этот оператор позволяет представить любое число аргументов в виде массива, элементы которого можно перебрать при помощи цикла. Вообще, к каждой функции уже привязан объект arguments — массив, состоящий из всех аргументов, переданных функции.
Но давайте представим, что мы хотим, чтобы наша функция создала нового студента со своими оценками и средним баллом. Удобнее будет записать первые два аргумента в две отдельные переменные, а все оценки поместить в массив, который можно перебирать.
Именно это позволяет нам сделать оператор оставшихся аргументов!
Примечание: createStudent — плохая функция, потому что мы не проверяем, существует ли grades.length и отличается ли от 0. Но так функцию легче прочитать, поэтому я не учитывал эти случаи.
Расширение свойств объектов
Чтобы понять эту часть, рекомендую прочитать предыдущие объяснения о применении оператора оставшихся аргументов к итерируемым объектам и параметрам функций.
Дополнительные материалы
Сокращенная запись свойств объектов
При записи переменной в свойство объекта, если у переменной то же имя, что и у свойства, можно сделать следующее:
Объяснение
Раньше (до ES2015), если вы хотели при объявлении нового литерала объекта использовать переменные в качестве его свойств, вам пришлось бы писать подобный код:
Как видите, приходится повторять одно и то же, потому что имена свойств объекта совпадают с именами переменных, которые вы хотите записать в эти свойства.
С ES2015, если имя переменной совпадает с именем свойства, можно использовать такую сокращенную запись:
Дополнительные материалы
Промисы
Промис (promise) — это объект, который может быть синхронно возвращён из асинхронной функции (Ссылка).
Промисы могут использоваться, чтобы избежать «ада обратных вызовов», и они всё чаще и чаще используются в современных JavaScript-проектах.
Пример кода
Пояснение
Когда вы делаете AJAX-запрос, ответ будет несинхронным, так как вы запрашиваете ресурс, на обработку которого требуется некоторое время. Ответ может быть вообще не получен, если запрашиваемый ресурс недоступен по какой-то причине (404).
Чтобы избежать таких ситуаций, в ES2015 были добавлены промисы. Промисы могут иметь 3 различных состояния:
- выполняется;
- выполнено;
- отклонено.
Предположим, мы хотим использовать промисы для обработки AJAX-запроса для получения ресурса X.
Создание промиса
Сначала создадим промис. Будем использовать GET-метод jQuery для создания AJAX-запроса к ресурсу X.
Как видно из рассмотренного примера, объект Promise принимает функцию-исполнитель, в свою очередь принимающую два параметра: resolve и reject . Эти параметры — функции, которые при вызове изменяют состояние промиса со значения выполняется на выполнено или отклонено соответственно.
Промис находится в состоянии выполняется после создания экземпляра, и его функция-исполнитель выполняется немедленно. Как только одна из функций выполнено или отклонено вызвана в функции-исполнителе, промис вызовет связанные с ним обработчики.
Использование обработчиков промисов
Чтобы получить результат (или ошибку) промиса, нужно назначить ему обработчики следующим образом:
Если вызов прошёл успешно, вызывается resolve и выполняется функция, переданная в метод .then .
Если вызов не прошёл, вызывается reject и выполняется функция, переданная в .catch .
Примечание: Если промис уже выполнен или отклонён на момент назначения соответствующего обработчика, обработчик всё равно будет вызван. Так что между выполнением асинхронной операции и назначением обработчиков не возникает состояние гонки. (Ссылка: MDN)
Дополнительные материалы
Шаблонные строки
Шаблонные строки — это конструкции, позволяющие использовать вставку, или интерполяцию выражений, в однострочных и многострочных строках.
Другими словами, это новый синтаксис записи строк, с которым удобно использовать любые выражения JavaScript (например, переменные).
Пример кода
Дополнительные материалы
Тегированные шаблонные строки
Шаблонные теги — это функции, которые могут быть префиксом к шаблонной строке. Когда функция вызывается таким образом, первый параметр представляет собой массив строк, которые выводятся между интерполированными переменными, а последующие параметры — значения выражений, вставленных в строку. Для захвата всех этих значений используйте оператор расширения . . (Ссылка: MDN).
Примечание: Известная библиотека, которая называется стилизованные компоненты, основана на этой возможности.
Ниже приведен пример работы тегированных шаблонных строк:
Более интересный пример:
Дополнительные материалы
Импорт / экспорт
Модули в ES6 используются для получения доступа к переменным и функциям из других модулей (файлов с кодом), причем экспорт этих переменных и функций должен быть четко обозначен в исходном модуле.
Крайне рекомендую почитать ресурсы MDN об экспорте/импорте (см. Дополнительные материалы ниже), в них содержится четкая и полная информация.
Объяснение с примером кода
Именованный экспорт
Именованный экспорт используется для экспорта нескольких значений из модуля.
Примечание: Вы можете именовать экспорт только объектами первого класса, у которых есть имя.
Хотя именованный импорт выглядит как деструктуризация, это не одно и то же. Кроме того, именованный импорт имеет другой синтаксис, не поддерживает значения по умолчанию и глубокую деструктуризацию.
Кроме того, можно создавать псевдонимы, но их синтаксис будет отличаться от синтаксиса, используемого при деструктуризации:
Импорт / экспорт по умолчанию
Что касается экспорта по умолчанию, то для каждого модуля (файла) может быть только один экспорт. Экспортом по умолчанию может быть функция, класс, объект или что-то еще. Это значение считается «основным», поскольку его будет проще всего импортировать. Ссылка: MDN.
Дополнительные материалы
this в JavaScript
Оператор this в JavaScript ведет себя не так, как в других языках. В большинстве случаев он определяется тем, как вызвана функция (Ссылка: MDN).
Это сложное понятие с множеством тонкостей, так что я крайне рекомендую вам тщательно изучить приведенные ниже Дополнительные материалы. Я покажу вам, как сам лично определяю, чему равно this . Этому меня научила вот эта статья Yehuda Katz.
Дополнительные материалы
Класс
JavaScript — это язык, основанный на прототипах (в то время как, например, Java — язык, основанный на классах). В обновлении ES6 представлены классы JavaScript, которые являются синтаксическим сахаром для наследования на основе прототипов, а не новой моделью наследования на основе классов (Ссылка).
Если вы знакомы с классами в других языках, слово «класс» может ввести вас в заблуждение. Постарайтесь не делать предположений о работе классов в JavaScript на основе других языков. Считайте это совершенно другим понятием.
Поскольку этот документ не является попыткой научить вас языку с нуля, я надеюсь, что вы знаете, что такое прототипы и как они себя ведут. Если нет, смотрите дополнительные материалы после примеров.
Примеры
До ES6, синтаксис на основе прототипов:
Начиная с ES6, синтаксис на основе классов:
Дополнительные материалы
Для понимания прототипов:
Для понимания классов:
Ключевые слова Extends и super
Ключевое слово extends используется в объявлении класса или в выражениях класса для создания дочернего класса (Ссылка: MDN). Дочерний класс наследует все свойства родительского класса и дополнительно может добавлять новые свойства или изменять унаследованные.
Ключевое слово super используется для вызова функций родителя объекта, включая его конструктор.
- В конструкторе ключевое слово super должно использоваться раньше, чем ключевое слово this .
- Вызов super() вызывает конструктор родительского класса. Если вы хотите передать какие-то аргументы из конструктора класса в конструктор родительского класса, то нужно вызывать функцию следующим образом: super(arguments) .
- Если у родительского класса есть метод X (даже статический), для его вызова в дочернем классе можно использовать super.X() .
Пример кода
Примечание: Если бы мы попытались использовать this перед вызовом super() в классе Square, произошёл бы ReferenceError:
Дополнительные материалы
Async Await
Помимо Промисов вам может встретиться еще один синтаксис для обработки асинхронного кода — async / await .
Цель функций async / await — упростить синхронное использование промисов и выполнить какое-либо действие над группой промисов. Точно так же, как промисы похожи на структурированные функции обратного вызова, async / await похожи на комбинацию генераторов и промисов. (Ссылка: MDN)
Примечание: перед тем как пытаться понять async / await , вы должны понимать, что такое промисы и как они работают, поскольку async / await основаны на промисах.
Примечание 2: await должен использоваться в async функции, что означает, что вы не можете использовать await на верхнем уровне вашего кода, так как он не находится внутри async-функции.
Пример кода
Объяснение с помощью примера кода
async / await построены на промисах, но позволяют использовать более императивный стиль кода.
Оператор async объявляет функцию как асинхронную, и данная функция всегда будет возвращать промис. В async-функции можно использовать оператор await для приостановки выполнения до тех пор, пока возвращаемый промис либо выполнится, либо будет отклонен.
Когда будет достигнут оператор return async-функции, промис выполняется с возвращаемым значением. Если внутри async-функции генерируется ошибка, состояние промиса изменится на rejected . Если async-функция не возвращает никакого значения, промис всё равно будет возвращен и выполнится без значения, когда выполнение async-функции будет завершено.
Оператор await используется для ожидания выполнения Промиса и может быть использован только в теле async-функции. При этом выполнение кода приостанавливается, пока не будет выполнен промис.
Примечание: fetch — это функция, возвращающая промис, который позволяет выполнить AJAX-запрос.
Давайте сначала посмотрим, как мы можем получить пользователя github с помощью промисов:
Вот эквивалент с использованием async / await :
Синтаксис async / await особенно удобен для построения цепочек взаимозависимых промисов.
Например, вам нужно получить токен для того, чтобы получить публикацию в блоге из базы данных, а затем информацию об авторе.
Примечание: Выражение await должно быть заключено в круглые скобки для вызова методов и свойств разрешенных значений в одной строке.
Обработка ошибок
Если мы не добавим блок try / catch вокруг выражения await , неперехваченные исключения — неважно, были ли они выброшены в теле вашей async-функции или во время ожидания выполнения await — отклонят промис, возвращенный из async-функции. Использование состояния throw в асинхронной функции — то же самое, что возврат промиса, который был отклонен. (Ссылка: PonyFoo).
Примечание: Промисы ведут себя так же!
С помощью промисов вот как бы мы обработали ошибки:
Эквивалент с использованием async / await :
Дополнительные материалы
Истина / Ложь
В JavaScript «истинность» или «ложность» значения определяется при вычислении этого значения в булевом контексте. Примером булева контекста может быть вычисление в условии if .
Любое значение будет приведено к true (истина), кроме:
- false (ложь);
- 0 ;
- «» (пустая строка);
- null ;
- undefined ;
- NaN .
Вот примеры булева контекста:
-
значение условия if .
Значение myVar может быть любым объектом первого класса (переменная, функция, логическое значение), но оно будет преобразовано в логическое значение, поскольку вычисляется в булевом контексте.
- После логического оператора NOT ! .
Этот оператор возвращает значение «ложь», если его единственный операнд может быть преобразован к значению «истина»; иначе он возвращает значение «истина».
- Конструктор объектов типа Boolean .
- Тернарный оператор.
Значение myVar вычисляется в булевом контексте.
Будьте внимательны при сравнении двух значений. Значения объектов (которые должны быть приведены к истине), не приводятся к булеву типу, а приводятся к примитивному типу в соответствии со спецификацией. Внутри, когда объект сравнивается с булевым значением, например, [] == true , выполняется [].toString() == true , происходит следующее:
Дополнительные материалы
Анаморфизмы и катаморфизмы
Анаморфизмы
Анаморфизмы — это фунции, которые отображают некоторый объект на более сложную структуру, содержащую тип объекта. Это процесс разворачивания простой структуры в более сложную.
Рассмотрим разворачивание целого числа в список целых чисел. Целое число — наш изначальный объект, а список целых чисел — более сложная структура.
Пример кода
Катаморфизмы
Катаморфизмы противоположны анаморфизмам: они берут объекты более сложной структуры и складывают их в более простые структуры.
Рассмотрим следующий пример функции product , которая принимает список целых чисел и возвращает простое целое число.
Пример кода
Дополнительные материалы
Генераторы
Другой способ написания функции downToOne — использование генератора. Чтобы создать объект типа Generator , нужно использовать объявление function * . Генераторы — это функции, выполнение которых может быть прервано, а затем продолжено с тем же контекстом (привязками переменных), сохраняющимся при всех вызовах.
Например, функция downToOne может быть переписана следующим образом:
Генераторы возвращают итерируемый объект. Когда вызывается метод next() итератор, она выполняется до первого выражения yield , которое указывает значение, которое должно быть возвращено из итератора или с помощью yield* , которое дегегирует выполнение другому генератору. Когда в генераторе вызывается выражение return , он будет помечать генератор как выполненный и возвращать значение из выражения return . Дальнейшие вызовы next() не будут возвращать никаких новых значений.
Пример кода
Выражение yield* позволяет генератору вызывать другую функцию-генератор во время итерации.
Name already in use
JavaScript является типизированным языком и имеет динамическую, слабую, неявную типизацию.
Статическая и динамическая
При стратической типизации типы устанавливаются на этапе компиляции. К моменту выполнения программы они уже установлены и компилятор знает, где какой тип находится.
Пример языков со статической типизацией: Java, C#.
При динамической типизации типы определяются во время работы программы.
Пример языков с динамической типизацией: Python, JavaScript.
Слабая и сильная
При слабой (нестрогой) типизации автоматически выполняется множество неявных преобразований типов даже при условии неоднозначности преобразования или возможности потери точности данных.
Пример языка со слабой типизацией: JavaScript.
При сильной (строгой) типизации в выражениях не разрешено смешивать различные типы. Автоматическое неявное преобразование не производится.
Пример языков с сильной типизацией: Java, Python.
Например, нельзя сложить число и массив.
Явная и неявная
При явной типизации тип новых переменных, функции, их аргументов и возвращаемых ими значений нужно задавать явно.
Пример языков с явной типизацией: C++, C#.
При неявной типизации задание типов производится автоматически компиляторами и интерпретаторами.
Пример языка с неявной типизацией: JavaScript.
Типы данных и переменные
Переменная состоит из имени и выделенной под это имя области памяти.
Имя переменной может содержать буквы, цифры, $, _.
Регистр важен ( ALL и all — разные переменные).
Константы принято называть в UPPERCASE: ANY_NAME .
6 примитивных типов и объект
- number 1 , 2.17 , NaN , Infinity
- string ‘str’ , «str»
- boolean true , false
- null null
- undefined undefined
- symbol Symbol(str)
- object<>
Значение null не является «ссылкой на нулевой адрес/объект» или чем-то подобным.
Значение null специальное и имеет смысл «ничего» или «значение неизвестно».
Значение undefined означает «переменная не присвоена».
Символ (Symbol) — уникальный и неизменяемый тип данных, используемый в качестве идентификатора для свойства объекта.
Символы являются неперечисляемыми (not enumerable), что делает их недоступными при переборе свойств.
Способы объявить переменную
Блок (Block Statement) — всё, что лежит внутри фигурных скобок <> .
Например, конструкции if-else , while , switch , try-catch , циклы for , функции содержат блоки. Тем не менее можно использовать блоки и без этих конструкций.
Переменная let имеет блочную область видимости, то есть её нельзя использовать за пределами блока, в котором она объявлена.
Переменную let нельзя использовать до её инициализации.
Переменную let нельзя объявить дважды с одним и тем же именем.
Переменная const имеет те же свойства, что и переменная let, но вдобавок её значение нельзя переопределить.
Переменная var является предком переменных let и const и не обладает их ограничениями.
Переменная var не имеет блочной области видимости.
Переменную var можно использовать до её инициализации значением. Такое поведение называется всплытием. Всплывает только объявление переменной, а её временным значением (до инициализации) становится undefined .
Переменную var можно объявить дважды (redeclaration) с тем же именем.
Переменная var, находящаяяся вне каких-либо функций, размещается в глобальном объекте. Например, это может быть window .
Глобальная переменная не является переменной как таковой, а является свойством глобального объекта (в JavaScript им является window , в NodeJS — global ). Поэтому, в отличие от остальных переменных, её можно удалить оператором delete .
Бинарный оператор + приводит значения либо к числам и совершает сложение, либо к строкам и совершает конкатенацию.
- Строковое.
- Числовое.
- Логическое.
Явное и неявное преобразование
Если преобразование типов происходит автоматически, то оно называется неявным. Этот тип преобразований характерен JavaScript. Обычно такие преобразования происходят при выполнении операций между операндами разных типов.
Если преобразование типов задаётся разработчиком вручную (явно), то оно называется явным.
В JavaScript есть множество способов явно преобразовать тип.
Пример явного преобразования в Java.
Преобразование объекта к примитивному значению
Если объект участвует в операции, подразумевающей использование примитивного значения, он должен быть приведён.
Каждый объект имеет метод valueOf() , который возвращает примитивное значение объекта. По умолчанию метод возвращает сам объект (непримитивное значение).
Метод valueOf() можно переопределить. Например, он переопределён у Date .
Помимо valueOf() , каждый объект имеет метод toString() , возвращающий строковое представление объекта.
Метод toString() также можно переопределить.
Объект приводится к примитивному значению при помощи функции toPrimitive(argument, preferredType) , работающей по следующему алгоритму.
- Если value — примитивное значение ( number , string , boolean , null , undefined ), то вернуть его.
- Иначе вызвать value.valueOf() . Если результат — примитивное значение, то вернуть его.
- Иначе вызвать value.toString() . Если результат — примитивное значение, то вернуть его.
- Выбросить исключение TypeError(‘Cannot convert object to primitive value’) .
По умолчанию параметр preferredType имеет занчение ‘number’ . Если передать ‘string’ , то шаги алгоритма с valueOf() и toString() меняются местами.
Преобразование к строке
Преобразование значения к типу string производится функцией ToString(argument) по следующим правилам.
- Если значение argument имеет тип string , то вернуть значение.
- Иначе, если argument имеет тип Symbol , выбросить TypeError .
- Иначе, если argument имеет примитивный тип number , boolean , undefined , null , обернуть его в строку и вернуть: «null» , «undefined» , «1.2» , «NaN» , «true» , «false» .
- Иначе, если argument имеет тип Object , вернуть результат ToString(ToPrimitive(argument)) .
Преобразование к логическому значению
Преобразование значения к типу boolean производится функцией ToBoolean(argument) по следующим правилам.
- Если argument имеет тип boolean , то вернуть значение.
- Иначе, если argument равно undefined , null , 0 , NaN , «» (пустая строка), то вернуть false .
- В остальных случаях ( Object , Symbol , числа кроме 0 и непустые строки) вернуть true .
Преобразование к числу
Преобразование значения к типу number производится функцией ToNumber(argument) по следующим правилам.
- Если значение argument имеет тип number , то вернуть значение.
- Иначе, если argument имеет тип boolean , вернуть 1 ( true ) или 0 ( false ).
- Иначе, если argument имеет тип string , попытаться преобразовать строку к числу или вернуть NaN в случае неудачи. Пустая строка приводится к нулю.
- Иначе, если argument имеет тип Symbol , выбросить TypeError .
- Иначе, если argument равно undefined , вернуть NaN .
- Иначе, если argument равно null , вернуть 0 .
- Иначе, если argument имеет тип Object , вернуть результат ToNumber(ToPrimitive(argument)) .
Оператор нестрогого равенства == производит неявное преобразование типа к числу (если оба операнда не являются строками).
Область видимости, лексическое окружение, замыкание
В любой момент выполнения кода некоторая переменная либо доступна, либо не доступна.
Видимость ( visibility ), доступность ( accessibility ) переменных отражает понятие область видимости, скоуп (англ. scope ).
Существует два типа области видимости: глобальная и локальная.
Глобальная область видимости (англ. Global Scope ) — это область видимости всей программы (всего скрипта). В браузере глобальная область видимости представлена объектом window . На NodeJS-сервере глобальная область видимости представлена объектом global .
Локальная область видимости (англ. Local Scope ) — это область видимости любой функции, объявленной в скрипте. Каждая функция при своём вызове создаёт локальную область видимости. Переменные, определённые внутри функции, недоступны извне.
Стоит избегать явного использования глобальной области видимости, если это возможно, и стараться использовать только локальную область видимости. В идеале следует писать код так, чтобы внешняя область видимости не содержала тех переменных, которые характерны какой-то определённой внутренней области видимости. С одной стороны, это отвечает принципу инкапсуляции: скрываются детали реализации (выставляется наружу только то, что необходимо). С другой стороны, это отвечает принципу модульности: в коде появляются самодостаточные блоки (модули, фичи, пакет, компоненты — их называют по-разному), которые хранят всё нужное внутри себя, не имеют внешних зависимостей и, таким образом, могут быть довольно легко экспортированы в другое место.
Область видимости определяется в момент вызова функции.
Замыкание (англ. closure ) — это функция вместе с ссылками на её окружение, называемое лексическим окружением (Lexical Environment). По сути говоря, любая функция в JavaScript представляет собой замыкание.
Для начала изолируем каждый интересующий нас случай, а затем посмотрим, как они работают все вместе.
Объявление переменной var всплывает в самое начало скрипта, что позволяет использовать имя этой переменной до её объявления. Тем не менее, значение переменной var изменяется в области видимости лишь при инициализации.
В JavaScript переменная может быть использована перед тем, как она была определена (declared) в коде.
Всплытие (англ. hoising ) — поведение JavaScript, размещающее объявления (англ. declarations ) вверху текущей области видимости (англ. current scope ).
При попытке использования необъявленной переменной выдаётся ошибка.
Всплывают (англ. hoist ) только сами объявления (англ. declarations ), но не присвоенные им значения (англ. initializations ).
Это связано с тем, что переменная создаётся в области видимости на первом этапе интерпретации, а инициализируется значением на втором этапе.
Всплывают переменные var и глобальные переменные, а let и const не всплывают: выдаётся ошибка.
Контекст, контекст выполнения и ключевое слово this
Прежде, чем приступать к определению контекста в рамках JavaScript, давайте разберёмся, что же такое контекст в широком смысле этого слова.
О контексте в литературе
Контекстом (англ. context ) называют совокупность фактов и обстоятельств, в окружении которых происходит некоторое событие, существует некоторое явление или некоторый объект.
Например, можно сфокусироваться на одной из следующих тем ниже и начать описывать их.
- Пример события. Например, исторические события — Битва под Оршей, принятие Билля о правах.
- Пример объекта. Например, историческая личность — Эммелин Панкхёрст и исторически значимое место — Собор Парижской Богоматери.
- Пример явления. Например, северное сияние. Сбор информации на любую тему выше равносилен наполнению контекста, соответствующего этой теме.
Чем больше мы узнаём о чём-то, тем точнее мы можем воспроизводить это. Чем больше рассказчик даёт вам деталей, тем детальнее вы представляете то, что он видел своими глазами.
Например, рассмотрим следующий пример описания человека.
Джек Лондон — это американский писатель, который отбрёл известность благодаря своим приключенческим рассказам и романам. Тем, кому довелось знать его лично, описывали его как мужественного, отважного, целенаправленного и решительного человека. Как видно, в первом предложении указано имя человека. И во втором предложение нам уже ясно, что местоимение «его» ссылается на Джека Лондона.
Если бы компьютер мог выделять контекст из написанного, то он бы сделал это примерно следующим образом:
С каждым новым предложением этот контекст бы продолжил наполняться новыми деталями.
Вырывание из контекста
Если убрать первое сообшение из примера выше, то тогда не понятно, о ком идёт речь. В таком случае говорят о нарушении целостности контекста или вырывании из контекста.
Тем, кому довелось знать его лично, описывали его как мужественного, отважного, целенаправленного и решительного человека.
Например, контекстом текущего документа Notes/JavaScript.md является язык JavaScript и всё, что с ним тесно связано. И в то же время любой другой язык программирования (скажем, Java) находится вне рассматриваемого контекста. Ещё пример: в контексте данной главы рассматриваются понятия «контекст», ключевое слово this и не рассматриваются типы данных.
Контекст выполнения в JavaScript
Если вернуться к JavaScript, то под «контекстом» обычно подразумевают контекст выполнения (англ. Execution Context , EC ).
Всего можно выделить два контекста выполнения:
- Контекст выполнения функции (англ. Function Execution Context , FEC ).
- Глобальный контекст выполнения (англ. Global Execution Context , GEC ).
Глобальный контекст выполнения
Оператор typeof возвращает тип аргумента.
Результатом действия оператора является строка.
В JavaScript массивы и функции так же являются объектами, но оператор typeof имеет тип «function» для удобства.
Оператор typeof cчитает null объектом, что является врождённой ошибкой JavaScript, которую не исправляют в целях поддержки совместимости с предыдущими версиями.
Тем не менее, null не является объектом как таковым: это примитивное значение.
Оператор typeof считает, что NaN (Not-a-Number) является «number» . Это объясняется тем, что NaN появляется только при операциях с числами, а также содержится в Number.NaN (как и метод Number.isNaN(value) ).
Оператор typeof и undefined
Раньше в JavaScript undefined являлся названием глобальной переменной, по умолчанию не имеющей значения. То есть переменная undefined имела примитивное значение undefined , но его можно было переопределить.
Из-за изменяемости (mutability) undefined не использовали явно, а получали другим способом.
Например, typeof foo.prop === ‘undefined’ .
Сейчас такой ошибки нет.
Оператор void — унарный оператор, выполнящий принимаемое выражение и возвращающий undefined.
Его можно использовать со скобками и без:
Преобразование Function Declaration в Function Expression для самовызывающихся функций (IIFE):
Избегание явного использования undefined, а также краткий способ его записать (иногда можно встретить в минифицированном коде):
Иногда нужно просто выполнить функцию, ничего не возвращая, но стрелочная функция в своей краткой форме всегда возвращает результат выражения, что может иногда приводить к неожиданным последствиям.
Можно себя обезопасить:
Здесь стоит обратить внимание, что код ниже выдаст ошибку: приоритет => ниже, чем у void .
Оператор запятая (comma operator) выполняет каждый из его операндов слева направо и возвращает значение последнего. Операнды могут быть представлены выражениями.
Оператор запятая имеет самый низкий приоритет среди операторов, что может стать причиной ошибок при неправильном использовании.
Ошибка выше связана с тем, что оператор присваивания = выполняется раньше, чем оператор запятая, поскольку имеет более высокий приоритет. Впереди стоит let , применяющийся ко всем операндам оператора запятая: let foo = 2 и let 3 (название переменной не может быть числом).
Избежать ошибки можно при помощи оператора группировки ( ) , имеющего самый высокий приоритет среди операторов.
К слову, пример ниже отработает без ошибок. В первом операнде foo = 2 происходит присвоение значения глобальной переменной, во втором просто возвращается 3 .
Не так часто удаётся применить оператор запятая, но иногда он может быть полезен.
Например, можно временно добавить в стрелочную функцию логирование, если нужно что-то быстро посмотреть.
Другой пример: выполнить операцию над чем-то и сразу вернуть её результат.
Здесь стоит ещё раз отметить важность оператора группировки.
Код выше воспринимается интерпретатором как const push = /* . */ и const arr (константы обязаны иметь какое-то значение при создании). С let ошибки бы не было.
Оператор delete — унарный оператор, удаляющий свойство из объекта (массива, функции и других наследников Object ).
При успешном удалении delete возвращает true (в том числе, если удаляется несуществующее свойство), false иначе.
При работе с массивами, delete создаёт дыры в них.
Оператор delete может удалить глобальную переменную, поскольку на самом деле она является свойством глобального объекта window .
Оператор delete не может удалять переменные var, let, const и функции.
Оператор delete не связан с очисткой памяти. Очиста памяти осуществляется сборщиком мусора при разрыве ссылок.
Перечисление свойств объекта
Цикл for. in перебирает все несимвольные (non-Symbol) перечисляемые свойства (enumerable properties) объекта, включая свойства из цепочки прототипов (prototype chain).
Метод Object.keys(obj) возвращает массив названий всех собственных (own) перечисляемых свойств объекта obj в том же порядке, в котором они обходились бы циклом for..in . Поскольку свойства собственные, цепочка прототипов не включается в перечисление.
Метод Object.getOwnPropertyNames(obj) возвращает массив названий всех собственных свойств объекта obj .
Является ли объектом
Такая реализация обусловлена следующим поведением оператора typeof.
В JavaScript объекты и массивы (тоже являющиеся объектами) передаются по ссылке (by reference).
Существует множество способов клонировать объект (object clone), среди которых есть плохие и хорошие.
Плохие способы клонирования
Клонирование через оператор присваивания = означает запись ссылки на объект в новую переменную. Если изменить эту переменную (не заменить полностью, а изменить поля), то изменится и оригинальный объект.
Следует избегать поведения, при котором изменение копии влияет на оригинальный объект.
Клонирование через Object.create() не имеет смысла, поскольку Object.create(proto) создаёт новый объект, используя существующий объект proto в качество прототипа для нового.
Клонирование в цикле for..in означает копирование не только собственных (own) свойств объекта, но и свойств прототипа. Само свойство, отвечающее за прототип, не копируется.
Клонирование через eval является самым худшим вариантом.
Во-первых, использование eval — это плохо.
Eval is not evil. Using eval poorly is.
Во-вторых, требуется поддержки функции uneval , имеющаяся только у Firefox, но даже в нём это может не сработать из-за политики безопастности контента (Content Security Policy): EvalError: call to eval() blocked by CSP .
Неглубокое клонирование (shallow clone) подразумевает копирование неглубоких свойств (shallow properties) оригинального объекта в новый объект. Если свойство само является объектом ( prop: <> ), то оно передаётся по ссылке (оригинальное и скопированное свойство ссылаются на один объект).
Неглубокое свойство obj.prop , глубокое свойство: obj.prop.nestedProp . Глубокие свойства (deep properties) объекта клонируются автоматически, поскольку содержатся в объектах, являющихся неглубокими свойствами.
Если вложенные объекты отсутствуют, неглубокое клонирование является оптимальным.
Клонирование через Object.assign().
Клонирование через Spread-оператор . работает аналогично Object.assign() .
Клонирование при помощи Object.keys() подразумевает перебор и копирование собственных свойств оригинального объекта.
В случае, если Object.assign и . не поддерживаются, можно написать полифилл с использованием Object.keys .
Аналогичного Object.keys поведения можно добиться от клонирования в цикле for..in, добавив в нём дополнительную проверку на принадлежность свойства.
Готовым решением неглубокого копирования является функция _.clone(value) из библиотеки lodash .
Глубокое клонирование (deep clone) подразумевает копирование свойств на всех уровнях, то есть на каждом уровне вложенности вместо передаче по ссылке создаётся новый объект с теми же свойствами.
Клонирование через JSON-сериализацию очень популярно благодаря простоте, скорости работы (JSON-сериализация реализована и оптимизирована браузером) и возможности глубокого клонирования.
Недостаток: утрата некоторых данных (data loss), а точнее тех данных, которые не поддерживаются в JSON.
Более того, некоторые данные вообще не могут быть преобразованы в JSON. Например, циклическая ссылка (англ. circular reference ) или BigInt вызовут исключение (ошибку), которое нужно будет где-то обработать.

Клонирование через V8-сериализацию в Node.js (экспериментальная функциональность).
Пример глубокого клонирования конкретного объекта без всяких функций.
Такое поведение можно было бы реализовать рекурсивной функцией cloneObject . Например,
Эта функция не может обработать все случаи. Например, отдельно следует описывать работу с массивами и функциями, а также с циклическими ссылками, выбрасывающими исключения «too much recursion» и подобные.
Таким образом, для глубокого клонирования лучше всего использовать готовые решения. Такими являются _.cloneDeep(obj) в библиотеке lodash , jQuery.extend(true, <>, obj) , angular.clone(obj) и другие.
В JavaScript есть два оператора сравнения: нестрогий (abstract) == и строгий (strict) === .
При сравнении объектов A и B оба оператора вернут true лишь в том случае, если ссылки A и B будут указывать на один и тот же объект.
Неглубокое сравнение (shallow comparison) объектов A и B подразумевает проверку на строгое равенство ( === ) только неглубоких свойств (shallow properties) объектов (проверка не рекурсивна). Если все неглубокие свойства совпадают, то объекты считаются эквивалентными (shallow equal). Если A === B , то A и B по определению считаются эквивалентными, поскольку ссылаются на один объект.
Неглубокое свойство obj.prop , глубокое свойство: obj.prop.nestedProp .
Примеры неглубокого сравнения.
- < a: 1 >и < a: 1 >считаются эквивалентными, поскольку их неглубокие свойства a совпадают ( 1 === 1 ).
- < a: <>> и < a: <>> считаются не эквивалентными, поскольку их неглубокие свойства a представленны объектами с разными ссылками ( <> !== <> ).
Реализация неглубокого сравнения для любых значений.
Применение неглубокого сравнения в React, чтобы сделать PureComponent .
Неглубокого сравнение применяется в Redux: shallowEqual(oldState, newState) , чтобы выяснить, изменился ли State. Именно поэтому очень важно не мутировать State: измененяются и новый, и старый State одновременно — неглубокое сравнение не видит различий между ними.
Глубокое сравнение (deep comparison) объектов A и B подразумевает рекурсивный обход и сравнение всех свойств (в том числе и глубоких) объектов A и B.
Одним из способов провести глубокое сравнения является JSON-сериализация (сравниваются получившиеся строки). Это достаточно быстрый способ.
Его большим недостатком является то, что порядок свойств в сравниваемых объектах имеет значение.
Других встроенных решений не существует.
Можно переписать функцию shallowEqual , сделав её рекурсивной, или подключить готовые функции из сторонних библиотек. В Node.js есть встроенная функция assert.deepEqual() , которая также представлена в виде отдельного модуля deep-equal .
Глубокое сравнение работает медленнее, чем неглубокое.
Не стоит его использовать, если в этом нет необходимости.
Мутабельность объекта — его способность изменяться после создания, мутация (mutation) — соответствующее изменение.
Объекты в JavaScript передаются по ссылке, поэтому по умолчанию являются мутабельными.
Переменная const разрешает мутацию объекта, поскольку хранит лишь ссылку на объект, которая не меняется (остаётся константой).
Для отслеживания мутаций раньше был доступен метод Object.observe() . Сейчас он запрещен (deprecated), поскольку были добавлены другие, более эффективные способы отслеживать мутации.
- obj — объект, изменения которого должны отслеживаться.
- callback — функция обратного вызова, принимающая массив объектов, описывающих изменения.
Метод Object.observe() работает асинхронно. Он возвращет массив объектов со всеми изменениями. Объекты в массиве расположены в том же порядке, в котором происходили изменения при выполнении скрипта.
MutationObserver — встроенный объект, отслеживающий изменения DOM-элементов.
- callback — функция обратного вызова, принимающая список объектов, описывающих изменения.
- element — DOM-элемент, изменения которого должны отслеживаться.
- observerOptions — объект с параметрами, определяющими, какие изменения должны отслеживаться.
Более развернётый пример.
Proxy (прокси) — встроенный объект, позволяющий не только отлавливать любое совершаемое над объектом действие, но и влиять на его исход.
При помощи Proxy можно временно откладывать совершение действия, производить валидацю, отмену дейсвтия, устанавливать значение по умолчанию (если устанавливаемое значение невалидно), логгирование и многое другое.
- target — проксируемый объект.
- handler — объект с методами-ловушками (traps), каждый из которых отвечает за определённый тип действий над объектом.
- set — запись свойства (внутренний метод [[Set]] ).
- get — чтение свойства (внутренний метод [[Get]] ).
- deleteProperty — удаление свойства (внутренний метод [[Delete]] ).
- has — проверка наличия свойства при помощи in (внутренний метод [[HasProperty]] ).
- construct — создание через new (внутренний метод [[Construct]] ).
- apply — вызов функции (внутренний метод [[Call]] ).
- getOwnPropertyDescriptor — переборы через Object.keys , Object.values , Object.entries , for..in и Object.getOwnPropertyDescriptor (внутренний метод [[GetOwnProperty]] ).
JavaScript накладывает условия на использование некоторых ловушек. Например, методы set и delete должны возвращать true , если изменения вступили в силу, и false — иначе.
Пример валидации перед установкой свойства проксируемому объекту (ловушка set ). Проверяется, что передаваемое значение также является объектом.
Пример логирования проксируемой функции при её вызове (ловушка apply ).
На примере выше заметно, что прямое взаимодействие с проксируемым объектом не имеет никакого эффекта — нужно всегда использовать созданный Proxy вместо него.
Reflect — встроенный JavaScript-объект, предоставляющий методы для всех действий, которые перехватывает Proxy (для каждой ловушки).
Reflect не является функциональным объектом, поэтому его нельзя вызвать как функцию или использовать в качестве конструктора. Все его методы статические.
- Reflect.get(target, property) эквивалетно target[property] .
- Reflect.set(target, property, value) эквивалетно target[property] = value . и так далее.
Пример создания экземпляра класса при помощи Reflect.construct .
Пример с установкой значения по умолчанию при помощи Proxy и Reflect.get .
Неизменяемый, иммутабельный (immutable) объект — объект, состояние которого не может быть изменено после создания.
Изменение иммутабельного объекта приводит к созданию нового объекта, но не затрагивает старый.
Иммутабельность затрагивает только сам объект, но не его свойства. Это работает как неглубокое копирование: ссылки на объекты-свойства остаются прежними.
Итерируемый объект (iterable) — любой объект, элементы которого можно перебрать в цикле for..of.
По умолчанию итерируемыми являются встроенные типы Array, Set, Map и String, в то время как Object не является.
Любой объект можно сделать итерируемым, реализовав метод Symbol.iterator .
В примере ниже реализуется итератор для объекта notes , содержащего буквенные значения по индексам. В функции итератора замкнуты две переменные: начальный и конечный индексы. Для реализации метода next() используется стрелочная функция, поскольку необходим доступ к буквам.
Массив (Array) — встроенный итерируемый объект (можно перебрать через for..of ), который хранит элементы по индексам 0, 1, 2, . , имеет свойство length , а также имеет доступ к методам Array.prototype ( find , includes , reduce и другие).
Создать массив можно двумя способами: через синтаксис [] или при помощи класса Array и его методов.
В массиве по некоторым индекстам могут лежать пустые элементы ( empty ).
Массив можно создать из любого итерируемого объекта при помощи Array.from(iterable) или оператора . .
Интересные примеры создания массивов.
Обращение к элементам массива
Обращение к элементам массива не отличается от обращения к объектам, то есть производится по ключу ( [] ).
Как и у обычного объекта, ключи массива являются строками.
Добавление и удаление элементов
Массив в JavaScript имеет методы, характерные двухсторонней очереди (deque, double ended queue), что позволяет достаточно просто добавлять и удалять элементы на обоих концах массива.
Добавлять элементы можно и при помощи оператора . .
Удаление при помощи delete создаёт пустую ячейку в массиве.
Является ли массивом
Сортировка (sorting) — упорядочивание элементов в списке (массиве) по какому-то правилу.
В JavaScript для сортировки массива имеется метод Array.prototype.sort(comparator) , принимающий в качестве аргумента компаратор (comparator) — функцию comparator(a, b) , задающую порядок сортировки. Если a и b равны, то функция должна вернуть 0 , если a > b — что-то больше нуля (например, 1 ), если a < b — что-то меньше нуля (например, -1 ).
Компаратор (в электронике) — устройство, принимающее два входных сигнала и определяющее, какой из них больше (возвращает 1 , если больше первый, 0 — если второй).
В объектно-ориентированных языках программирования компаратор может быть классом или интерфейсом, имеющим метод compare .
Если не задать компаратор в методе sort() , то применится компаратор по умолчанию, сравнивающий элементы в лексикографическом порядке (как строки, посимвольно).
Определим компараторы для сортировки массива из чисел по возрастанию (ascending) и по убыванию (descending).
Аналогично можно сортировать и более сложные сущности.
Например: объекты по их конкретным полям.
Псевдомассив (pseudo-array) — обычный объект, который как и массив, в качестве ключей имеет индексы 0, 1, 2, . и свойство length , но при этом не является итерируемым и не имеет доступа к методам Array.prototype .
Псевдомассив можно сделать итерируемым объектом.
Примером итерируемого псевдомассива является arguments , хранящий все аргументы функции function , в которой он используется.
Параметры и аргументы функции
Параметры функции — имена, перечисленные в определении функции.
Аргументы функции — значения, передаваемые в функцию.
Параметр функции является переменной, копирующей значение аргумента.
Фактически, параметры ведут себя следующим образом.
Поскольку в JavaScript примитивные значения копируются напрямую, а объекты передаются по ссылке, имеем следующее поведение параметров фунцкии.
Стрелочная функция (Arrow Function Expression) является функциональным выражением, которое, помимо укороченного синтаксиса, обладает рядом свойств по сравнению с функциональным выражением, объявленным через function (Function Expression).
Стрелочная функция не имеет своих this и arguments — их значения ищутся снаружи (из внешнего лексического окружения).
Отсутствие своего this влечёт за собой другую особенность: стрелочная функция не может быть использована как функция-конструктор, то есть не может быть вызвана с конструкцией new .
Ещё одной интересной особенностью стрелочных функций является то, что оператор => имеет очень низкий приоритет, что делает невозможным использование стрелочных функций в качестве операндов других операторов.
Например, следующий пример вызовет ошибку, поскольку у void приоритет выше, чем у => , и он обрабатывается раньше.
С async такой ошибки не возникает, поскольку async вообще не является оператором, поскольку не рассматривается отдельно от function .
Error и его наследники
ReferenceError — ошибка при обращении к несуществующей переменной.
SyntaxError — ошибка при попытке интерпретировать синтаксически неправильный код.
TypeError — ошибка при наличии значения несовместимого (неожидаемого) типа.
RangeError — ошибка в случае нахождения значения за пределами допустимого диапазона.
EvalError — ошибка в глобальной функции eval(). В текущей спецификации не используется и остаётся лишь для совместимости.
Ошибка ниже связана с проведением браузерами политики безопастности контента (Content Security Policy), которая помогает избежать многих потенциальных XSS (cross-site scripting) атак.
Ранее её тип был EvalError, сейчас он просто опускается:
URIError — ошибка при передаче недопустимых параметров в encodeURI() или decodeURI().
InternalError — внутренняя ошибка в движке JavaScript. (только Firefox)
Все рассмотренные типы ошибок можно сгенерировать так же, как и Error, наследниками которого они являются:
Promise (промис) – специальный объект, содержащий своё состояние.
Изначально состояние pending (ожидание).
Затем либо resolved / fulfilled (выполнено успешно), либо rejected (выполнено с ошибкой).
Функция executor(resolve, reject) вызывается автоматически. В ней можно выполнять любые асинхронные операции. По их завершении следует вызвать либо resolve(value) , либо reject(reason) .
После вызова resolve или reject промис меняет своё состояние, которое становится конечным (больше его изменить нельзя).
Отреагировать на изменение состояния промиса можно при помощи then и catch .
Пример с setTimeout, где промис успешно выполнится не менее, чем через 3 секунды.
Пример с передачей значения в resolve .
Пример с передачей причины в reject .
Промисификация – создание обёртки, возвращающей Promise, вокруг асинхронной функциональности.
Обычно промисифицируют асинхронные функции, построенные на функциях обратного вызова (callbacks).
Если нужно выполнять асинхронные операции в определённой последовательности, можно каждую из них обернуть в промис и создать цепочку промисов (Promise chain). Для создания такой цепочки необходимо в .then() или .catch() вернуть промис.
Порядок в Promise.all()
Функция Promise.all(iterable) принимает итерируемый объект (обычно массив), содержащий промисы (элементы, не являющиеся промисами, помещаются в Promise.resolve() ), дожидается выполнения каждого из промисов и возвращает массив, состоящий из их значений.
Несмотря на то, что промисы выполняются асинхронно, порядок в результирующем массиве значений совпадает с порядком промисов в начальном итерируемом объекте благодаря внутреннему свойству [[Index]]:
Работа с данными в JavaScript
![]()
В данной статье мы научимся работать с переменными в языке JavaScript. Научимся их создавать, изучим основные типы данных, преобразования и операторы.
Переменные
Во вводном уроке мы написали пару простых скриптов, например вывели приветствие в консоль, так же мы знаем о том что JavaScript позволяет производить вычисления, например, так:
Но в реальной программе нет смысла производить вычисления и сразу же выводить результат на экран. Необходимо сохранять его для того чтобы использовать в дальнейшем.
Для этого необходимо получить некоторую именованную область памяти, в которой можно будет сохранить наши данные.
Как раз для для этого в JavaScript существуют переменные и константы. Чтобы объявить переменную необходимо поставить ключевое слово let и затем через пробел написать ее имя.
Давайте попробуем создать простую переменную которая будет хранить сумму сложения двух чисел:
В данном примере мы создали переменную с именем sum и записали в нее результат сложения двух чисел используя оператор присваивания = .
Теперь можно использовать значение этой переменной, например, вывести его в консоль:
Комментарии
Во время написания кода может возникнуть необходимость что-то объяснить или оставить какие-то заметки. Для этого в JavaScript предусмотрены комментарии. Комментарий — это обычный текст, выделенный специальными символами.
Интерпретатор игнорирует комментарии, так что мы можем разместить в них любой текст. Также это может быть использовано чтобы временно исключить или как говорят “закомментировать” участок кода.
Комментарии бывают двух видов:
- Однострочные, который начинаются с символа // .
- Многострочные, начинаются с символа /* и заканчиваются символом */ .
Комментарии очень важны, они могут объяснить сложные участки кода или дать описание скрипта или метода. Но также важно не переусердствовать, не стоит объяснять то, что уже и так и описано в коде:
Данный комментарий является плохим так как разработчик, читающий ваш код, и так поймет что вы создали переменную и комментарий только усложнит чтение кода.
Важно держать их актуальными, так как устаревший комментарий не принесет пользы или даже может сбить с толку читающего.
Также если у вас слишком большое количество комментариев, которые объясняют ваш код, то стоит задуматься, возможно код слишком сложный и его необходимо упростить.
Точка с запятой
Каждая строка в примере заканчивается точкой с запятой. Она служит разделителем для операций. Не обязательно заканчивать каждую операцию этим символом, но в некоторых ситуациях ее отсутствие может быть ошибкой.
Например, если разместить несколько операций без переноса строки и не разделить их точкой с запятой то мы получим ошибку:
В случае наличия точки с запятой код отработает верно:
Существует несколько подходов к расстановке точек с запятыми, один из них всегда ставить точки с запятой в конце каждой операции, чтобы точно избежать ошибок.
Инициализация
В последних примерах мы создавали переменную и сразу же присваивали ей значение, это называется инициализацией.
Инициализация — присваивание значения переменной сразу же в момент создания. Это экономит место, а так же необходимо для создания констант.
Константы
Константа — это переменная, значение которой не может быть изменено в дальнейшем. Такая переменная обязательно должна быть инициализирована сразу же в момент создания.
Константы могут пригодиться, если необходимо сохранить значение, которое не будет изменяться в дальнейшем. Они создаются аналогично переменным, но вместо let используется const :
Также помните, отсутствие инициализатора или попытка переопределить константу вызовет ошибку:
Множественное объявление
Имеется возможность объявить несколько переменных используя одно ключевое слово let или const :
Данный код является полностью рабочим. Я советую придерживаться одного стиля создания переменных (то есть, не смешивать разные стили).
А когда вы попадете на реальный проект, проблема выбора отпадет сама собой. Вы просто будете придерживаться стиля используемого на проекте.
Объявление переменных в стиле ES5
Ключевые слова let и const были добавлены в шестой версии языка. Ранее для объявления переменных использовалось ключевое слово var , которое было похоже на let , но имело ряд отличий. Аналогов ключевому слову const не было.
Основные отличия:
Современные переменные могут быть созданы только один раз, попытка создать несколько переменных с одним именем вызовет ошибку. Используя var мы можем создать сколько угодно переменных с одинаковыми именами.
Использование до объявления. К переменным, созданным с помощью let и const , нельзя обратиться до их создания. К переменным созданным с помощью var , можно обратиться до создания. Это вызвано так называем процессом “поднятия” (hoisting). До присваивания значения в переменной находится специальное значение — undefined .
Именование переменных
У каждой переменной должно быть имя. В примерах выше использовались простые имена переменных, например a или b . Такие имена подойдут только для небольших примеров, в реальных проектах переменные надо называть правильно и осмысленно.
Правила именования переменных:
- Имя переменной (идентификатор) может состоять из любых букв английского алфавита, цифр, нижнего подчеркивания _ , символа доллара $ и некоторых других символов Unicode.
- Имя переменной не должно начинаться с цифры.
- Имена переменных регистрозависимы, то есть Sum и sum это две разные переменные.
- Некоторые идентификаторы уже используются языком. Они называются ключевыми словами и не могут быть использованы в именах переменных, например: if или for .
Полный список ключевых слов.
Как правильно называть переменные:
Эти советы помогут называть переменные понятными и простыми именами. Некоторые из них являются субъективными, но все равно их стоит прочитать и обдумать:
- Имя переменной должно быть существительным, которое правильно отображает её сущность.
- Используйте camelCase в именах переменных.
- Не используйте сокращение или аббревиатуры без необходимости. То что может казаться очевидным вам, может быть не понятно другому человеку. Может даже дойди до того, что программист сам через время не вспомнит что хранится в этой переменной.
Переменные: заключение
Итак, переменные это именованные ячейки памяти, которые нужны чтобы хранить информацию. В следующем разделе мы рассмотрим какие именно виды информации существуют в JS.
Типы данных в JavaScript
В примерах выше мы записывали в переменные только числа, на самом делеJavaScript может работать с данными разного типа.
В JavaScript 7 типов данных:
- Число (number);
- Строка (string);
- Логический (boolean);
- null;
- undefined;
- Объект (object);
- Символ (symbol).
typeof
Оператор typeof позволяет получить тип переменной в виде строки:
В JavaScript существует странная ошибка, для null оператор возвращает 'object' , хотя логично было бы получить значение 'null' .
Число (number)
Числа один из самых часто используемых типов данных в большинстве языков программирования. В отличие от некоторых других языков, в JavaScript нет отдельных типов данных для целых и дробных чисел.
Над числами можно производить арифметические операции (подробнее в разделе об операторах). Так же существует специальный объект Math , в котором есть многие математические функции для работы с числами (квадратный корень, степень, синус и т.д.). Его мы рассмотрим на следующих уроках.
Дробные числа в качестве разделителя используют точку, например 3.14 или 0.002 . Для хранения чисел используется формат IEEE 754.
Стоит быть осторожным, некоторые операции с дробными числами могут дать неверный результат из-за некоторых проблем в формате хранения чисел:
В примере ожидается получить 0.3 , но вместо этого получаем неожиданный результат. Данная проблема присуща не только JavaScript, но и многим другим языкам.
На данном этапе вам стоит просто знать об этой проблеме. Одним из возможных решений может быть округление чисел, но надо понимать, что такое решение не всегда подойдет.
Специальные значения:
Так же существует ряд специальных числовых значений:
- Бесконечность и минус бесконечность (infinity и -infinity). Бесконечности в javaScript могут получиться в результаты деления на 0, или если в результаты операции получается слишком большое или слишком маленькое число. Любые операции с бесконечностью дают в результате бесконечность.
- Не число (NaN). Это значения получается в случае некорректных операций с числами, например умножение числа и строки или попытка разделить ноль на ноль. Любая операций с NaN в результате приведет к NaN . Также NaN единственное значение в JavaScript которое не равно самому себе.
Строка (string).
Строковый тип предназначен для хранения текстовых данных. Текст который вы сейчас читаете является строковым типом данных, как и весь остальной текст на данной странице.
В JavaScript нет отдельного типа для хранения конкретного символа, только тип string для хранения строк. Для представление строк используется Unicode.
Чтобы создать значение этого необходимо написать строку текста в одном из двух видов кавычек: "Hello, World" или 'Hello, World' . Эти два варианта полностью идентичны, вы можете использовать любой из них.
Стоит быть аккуратными со строками, которые содержат вложенные кавычки. Если мы напишем строку вида 'My name's Jenya' , мы получим ошибку, так как JavaScript пример эту часть 'My name' за строку, и не поймет что делать с остальной частью выражения.
Чтобы избежать этого необходимо использовать разные виды кавычек, например, так:
Экранирование символов
Еще одним способом избежать этой проблемы будет экранировать символ кавычки. Необходимо поставить символ обратной косой строки \ перед кавычкой:
Существуют другие полезные специальные символы, например перевод строки \n , подробнее о ни вы можете почитать тут.
Индексация строк
Можно обратиться к конкретному символу в строке используя квадратные скобки [] и индекс (порядковый номер символа). Индексация строк (как и массивов) начинается с нуля. Результатом этой операции будет подстрока из одного символа.
Логический (boolean)
Логический тип представлен только двумя значениями true и false . Они получаются в результаты различных сравнений, который мы разберем в дальнейшем, когда будет говорить об операторах.
True означает истину или ответ “Да”.
False означает ложь или ответ “Нет”.
Они широко используются в сравнениях и условиях, позволяя нашему коду вести себя по разному в зависимости от входных данных.
Небольшой пример:
undefined
Что храниться в переменной в случае, если ее значение не задано? На случай обращение к такой переменной в JavaScript существует специальное значение undefined (не определено).
Данный тип выражен всего одним значением с таким же названием. Математические с undefined приведут к NaN .
Так же при обращение к несуществующему индексу в строке или массиве или несуществующему ключу в объекте (рассмотрим далее) мы так же получим undefined :
Этот тип данных очень похож на undefined , но имеет другое предназначение. Если undefined означает что значение не определено или не существует, то null означает что значение определено, но еще неизвестно.
Например если мы создали переменную, в которой будет храниться имя пользователя, но в данный момент оно неизвестно, мы запишем в нее значение null .
Также стоит заметить, что в математических операциях null превращается в 0, и не превращает результат в NaN , но злоупотреблять этим не стоит.
Символ (symbol)
Этот тип данных появился в шестой версии языка, и используется крайне редко. Он предназначен для создание уникальный идентификаторов (имен), и не является аналогом типа данных char в других языках программирования.
Необходимость использовать символы у вас возникнет нескоро. Он больше нужен был самим разработчикам языка, для того что реализовать новый функционал, не поломав совместимость со старыми версиями.
Подробнее о символах вы можете прочитать в этой статье.
Объект (object)
Все прошлые типы данных были простыми, то есть у нас была переменная какого-либо типа и одно значение.
Предположим необходимо сохранить некоторую информацию о пользователе, например имя и возраст, мы могли бы создать две отдельные переменные и записать необходимую информацию:
Вторым подходом было бы создать один составной объект, который хранит в себе информацию о пользователе:
Особенности синтаксиса объектов:
- Для создания объекта используются фигурные скобки < >.
- Объект представляет собой пары ключ — значение, разделенные двоеточием.
- Пары разделяются через запятую, после последней пары запятая необязательна.
- Значением может являться любой тип, в том числе другой объект.
- Ключом может выступать любое значение (в отличие от имени переменной), например ключ может быть числом, ключевым словом или состоять из двух слов:
Обращение к значениям в объекте
Чтобы получить значение которое храниться в объекте под заданным ключом необходимо написать имя объекта поставить символ точки и затем написать необходимый ключ:
Также есть второй способ получить значение из объекта, используя квадратные скобки [] . Ключ необходимо разместить внутри скобок. Он может пригодиться в таких ситуациях:
- Имя ключа находиться в переменной.
- Ключ состоит из несколько слов.
- Ключ является числом.
В случае обращения по несуществующему ключу получаем значение undefined .
Объект можно динамически расширить, дописав ему новые свойства используя любой из двух способов:
В примере выше есть некоторая странность, объект объявлен как константа, но мы смогли добавить к нему новые поля. На самом деле это поведения является правильным, ключевое слово const относится к самому объекту, то есть, мы не сможем заменить его на другой объект или очистить эту переменную. Но изменить значения его полей вполне возможно.
В отличие от этого, строки являются неизменными, то есть, в строках мы не сможем взять и изменить отдельный символ.
Копирование объектов
Стоит быть осторожным копируя объекты. Разберем такую ситуацию, у нас есть некоторая переменная a в которой находится объект. Мы создаем переменную с именем b и присваиваем ей переменную a .
Мы ожидаем получить в b копию объекта a , но этого не происходит. Вместо этого обе переменные получают ссылку на один объект. И любая модификация вызовет изменения в обеих переменных:
На собеседованиях часто задаются вопросы на подобную тематику. Чтобы избежать этого поведения, необходимо скопировать объект, например так:
Object.assign создает новый объект из полученных объектов. Как именно это работает вы поймете позже, пока что главное понимать концепцию.
Массив (Array)
Массивы не являются отдельным типом данных, а являются подмножеством объектов. Массив представляет собой непрерывную последовательность данных к которым можно обращаться по индексам.
Для объявление используются квадратные скобки [ ] , внутри которых элементы массива перечисляются через запятую, обращение к элементу массива происходит через индекс:
Массивы подходят, если элементы представляют собой однотипную информацию и важен сам порядок (например список пользователей или статей).
С помощью свойства length можно получить количество элементов в массиве. Также его можно изменить увеличив или уменьшив длину массива. В случае увеличения добавятся пустые элементы ( undefined ).
В случае обращения по несуществующему индексу мы получим значение undefined .
Типы данных: заключение
Мы рассмотрели типы данных языка JavaScript, их особенности и применение. У каждого типа данных существует множество встроенных методов облегчающих работу с ними, вы узнаете о них из следующих глав.
Подробнее почитать о типах данных вы можете в разделе “структуры данных” на learn.javascript.ru.
Преобразования типов
Предположим у нас есть следующий код:
Мы пытаемся сложить данные разного типа, что должно получиться в результате такой операции? В некоторых языках подобная операция вызвала бы ошибку, но в JavaScript мы получаем следующий результат:
Что произошло? Число 20 превратилось в строку '20' , и дальше произошла конкатенация двух строк. Такое превращение называется преобразованием типов.
Преобразование типа — изменение типа переменной.
В JavaScript есть 3 типа преобразования:
- Строковое
- Численное
- Логическое
Строковое преобразование
Строковое преобразование происходит при конкатенации (сложении со строкой) или вызове функций, которые выводят текст на экран, например alert или console.log .
Преобразование происходит ожидаемым образом, значение становится строкой, например: число 20 станет строкой '20' , а логическое значение true строкой 'true' .
Также значение можно явно преобразовать с помощью функции String .
Численное преобразование
Преобразование в число происходит при работе с математическими функциями, сравнениях (например больше > или меньше < ). Также можно преобразовать значение в число с помощью функции N .
Преобразование происходит по следующим правилам:
- null преобразовывается в 0.
- undefined преобразовывается в NaN .
- true / false преобразовываются в 1 / 0.
- Строка преобразовывается к числу если состоит только из цифр и пробелов по краям, в случае наличия других символов получаем NaN :
Логическое преобразование
Преобразование в логический тип происходит, если значение помещается в условие if , или к нему применяется оператор отрицания ! (условия и операторы рассмотрим далее).
Преобразование происходит по двум простым правилам:
- null , undefined , NaN , 0 , '' (пустая строка) становятся false .
- Остальное — true .
Также преобразование может быть вызвано функцией Boolean :
Преобразования типов: заключение
Существуют 3 типа преобразования, которые вызываются в различных ситуациях и имеют соответствующие функции:
- Численное (Number).
- Строковое (String).
- Логическое (Boolean).
Преобразования пригодятся в дальнейшем изучении языка, более подробно о них можно прочитать в этой статье на learn.javascript.ru.
Основные операторы
Для работы с нашими данными в JavaScript предусмотрен набор операторов, который позволит производить различные действия над нашими данными, например складывать, сравнивать и т.д.
Вы уже сталкивались с некоторыми операторами в жизни и ранее в этом уроке, например с оператором сложения + .
В данном примере мы видим оператор + , который применяется к числам 2 и 3 , сами числа являются операндами.
Операнд — то над чем выполняется операция.
Операторы можно поделить на бинарные и унарные:
Бинарный оператор выполняется с двумя операндами, например:
Унарный оператор выполняется лишь с одним операндом:
Оператор присваивания
Мы уже использовали этот оператор ранее ( = ), он позволяет присвоить переменной новое значение. Данный оператор обладает одним из самых низких приоритетов, этот означает что оператор будет выполнен в последнюю очередь.
Например в выражении ниже, сначала будет выполнено сложение и лишь затем присваивание результата в переменную:
Математические операторы
JavaScript поддерживает основные арифметические операции:
- Сложение +
- Вычитание —
- Умножение *
- Деление /
- Возведение в степень **
- Остаток от деления %
Большинство этих операций знакомы нам еще со школы. Они выполняются в математическом порядке:
- Выполнение идет слева направо.
- Умножение имеет наивысший приоритет.
- Приоритет деления выше сложения и вычитания.
- Скобки позволяют изменить порядок выполнения операций.
Для всех операторов, кроме сложения + , вызывается численное преобразование операндов. Если один из операндов бинарного + является строкой, то выполняется строковое преобразование операндов и конкатенация (сложение строк):
Остаток от деления
Данная операция находит целочисленный остаток от деления. В примере выше остаток от деления 13 на 6 равен 1 . Это означает следующее: число 6 померещатся в число 13 ровно 2 раза, а в остатке имеем 1.
Также эта операция называется делением по модулю. Деление по модулю на 2 позволяет понять, является ли число четным, так как только только в случае четных чисел в результате этой операции будет получен 0 .
Сокращенные операции
Предположим мы хотим прибавить к значению переменой единицу. Для этого нужен следующий код:
Разберем данный код подробнее:
- В переменной num находится число 10 .
- Складываем значение num с 1 .
- Результат записываем обратно в num .
Специально для таких операций были придуманы сокращенные формы, объединяющее операцию присваивания с другим оператором:
Данные операции полностью аналогичны своим полным формам и сделаны просто для удобства.
Инкремент и декремент
В программировании часто нужны операции увеличения или уменьшения на единицу. Для этих целей существует специальные унарные операторы:
- Инкремент ++ — увеличение на единицу
- Декремент — — уменьшение на единицу
Оба оператора имеют две формы:
- Постфиксная форма.
- Префиксная форма.
В данном примере кода кажется, что формы полностью аналогичны. Мы сможем заметить разницу если присвоим результат операции другой переменной:
Из этого примера видно следующее поведение: при использовании префиксного инкремента, значение переменной a было присвоено значение после увеличения на единицу.
В случае использования постфиксной версии, значение переменной b было присвоено значение до увеличения на единицу.
Операторы сравнения
Следующая группа бинарных операторов используется для сравнения значений, чаще всего числовых и, иногда, строковых. Все операторы этой группы возвращают в результате значения логического типа, true или false . Например: равно или не равно, меньше или не меньше.
Равенство ==
Для сравнения двух величин в JS присутствует оператор равенства == , он возвращает true если значения равны и false в обратном случае. Перед сравнением значения будут приведены к одному типу:
В данном случае строка '3' равна числу 3 так как строка была преобразована к числу 3 , и мы получили одинаковые значения слева и справа.
Не равенство !=
Является противоположностью оператора равенства, проверяет не равны ли значения:
Например, число 2 не равно числу 3 поэтому оператор возвращает true .
Строгое равенство === и строгое неравенство !==
Преобразования операндов в сравнениях могут привести к неожиданным и ненужным результатам. Предположим нас не устраивает ситуация что число 3 и строка '3' равны. В таком случае необходимо использовать строгое равенство.
Строгое равенство не выполняет преобразование и сначала сравнивает тип переменных, и в случае не совпадения сразу же возвращает false :
Оператор строго неравенства работает аналогичным образом.
Более подробно о преобразованиях во время сравнений вы можете почитать в этой статье на MDN. Я рекомендую всегда использовать === и !== , чтобы избежать неожиданного поведения и сравнивать только значения одинакового типа.
Больше, меньше и прочее
Помимо операторов равенства и неравенства в языке присутствуют следующие операторы:
- Больше >
- Меньше <
- Больше или равно >=
- Меньше или равно <=
Принцип работы этих операторов также известен из школьного курса математики. Эти операторы преобразовывают свои операнды к числам, за исключением ситуации сравнения двух строк:
Желательно использовать эти операции для сравнения чисел с числами или строк со строками. В остальных случаях вы получите преобразования и не совсем очевидное поведение.
Логические операторы
Предположим надо проверить одновременно несколько условий или выполнение одного из нескольких условий, например, проверить что число находится в промежутке между 10 и 20 .
Для сложных логических операций в JavaScript существуют специальные логические операторы:
- Логические И &&
- Логические ИЛИ ||
- Логическое НЕ !
- Исключающее ИЛИ ^
Логическое И
Этот оператор проверяет выполняются ли одновременно несколько условий.
Логическое ИЛИ
Этот оператор проверяет выполняется ли хотя бы одно из условий
Логическое НЕ
Этот оператор инвертирует результат логической операции, меняет true на false и наоборот.
Исключающее ИЛИ
Этот оператор проверяет выполняется ли хотя бы одно из условий, но не оба сразу.
Подробнее работу операторов вы можете рассмотреть на примере, в реальной программе вместо значений true или false будут находиться другие логические операции, операции сравнения или переменные с типом boolean :
Работа с логическими операциями и принципы их работы изучает дискретная математика. Это одно из самых важных направление математики для программиста.
Операторы: заключение
В данном разделе мы рассмотрели основные операторы JavaScript. Мы рассмотрели не все операторы, например можете почитать про побитовые операторы. Полный список операторов и их приоритетов можно посмотреть тут
Что дальше?
В этом уроке мы рассмотрели типы данных, переменные и операторы. В следующем уроке будут условия и циклы.
В данной статье много ссылок на другие ресурсы, в том числе документацию MDN. Читать ее труднее чем учебные материалы, но в ней больше информации и стоит начать привыкать. В дальнейшей работе вам часто придется работать с подобными ресурсами.