- PVSM.RU - https://www.pvsm.ru -
В течение первых нескольких лет использования JavaScript я чувствовал себя чуть ли не самозванцем. Даже хотя я и мог создавать веб-сайты с помощью фреймворков, я ощущал, что мне чего-то не хватает. Собеседования по JavaScript внушали мне страх из-за того, что у меня не было чёткого понимания основ этого языка.
За многие годы я сформировал ментальную модель JavaScript, которая дала мне ощущение уверенности. Здесь я собираюсь поделиться с вами весьма сжатым вариантом этой модели. Её структура напоминает словарь. Каждое понятие описано в нескольких предложениях.
По мере того, как вы будете читать этот материал, попробуйте мысленно оценить то, насколько вы уверенно чувствуете себя по отношению к каждому рассматриваемому здесь вопросу. И если окажется так, что многое отсюда покажется вам не особенно знакомым, я вас за это не осужу. Но если это и правда так — в конце материала есть то, что поможет вам исправить ситуацию.
1
, 2
и 420
— это значения. Но значениями являются и другие сущности. Например — предложение "Cows go moo"
. Правда, не всё является значением. Число — это значение, но инструкция if
— это уже не значение. Ниже мы ещё поговорим о различных видах значений.
420
, строки — такие, как "Cows go moo"
, объекты. Есть и другие типы значений. Узнать тип значения можно с помощью оператора typeof
. Например, команда console.log(typeof 2)
приведёт к выводу в консоль number
.2
— это будет одно и то же значение 2
. В программе нельзя «создать» ещё одно значение 2
, или сделать так, чтобы 2
«превратилось» бы в 3
. Это справедливо и для строк.null
и undefined
. Это — два особых значения. Они не такие, как другие, из-за того, что с ними много чего нельзя делать — их появление часто приводит к ошибкам. Обычно использование null
представляет собой указание на то, что некое значение не было назначено переменной умышленно, а undefined
говорит о том, что некое значение отсутствует по случайности. Однако то, как именно использовать эти значения, программист решает сам. Эти значения существуют из-за того, что иногда лучше, чтобы в ходе выполнения некоей операции произошла бы ошибка, а не случилось бы так, что выполнение программы продолжилось бы после «обработки» несуществующего значения."Cows go moo" === "Cows go moo"
и 2 === 2
. И тут всё понятно: 2
— это 2
. Обратите внимание на то, что мы используем три знака равенства, которые представляют вышеописанную концепцию равенства значений в JavaScript.
==
). Сущности могут быть признаны нестрого равными друг другу даже в том случае, если они представлены различными значениями, выглядящими похожими друг на друга (нечто вроде 2
и "2"
). Оператор нестрогого равенства был добавлен в JavaScript на ранних стадиях становления языка, для удобства. С тех пор он является бездонным источником путаницы. Концепцию нестрогого равенства нельзя назвать фундаментальной, но она является типичным источником ошибок. Вы можете изучить оператор нестрогого равенства в какой-нибудь дождливый день, но многие стараются попросту не использовать оператор ==
.2
— это числовой литерал, а "Banana"
— это строковой литерал.let message = "Cows go moo"
. После того, как в коде была использована подобная конструкция, везде, где понадобится предложение "Cows go moo"
, можно писать просто message
, а не повторять это предложение. Позже можно поменять message
, сделав так, чтобы переменная указывала бы на что-то другое. Например, воспользовавшись такой конструкцией: message = "I am the walrus"
. Обратите внимание на то, что это не меняет самого значения. Это влияет лишь на то, на что именно ссылается переменная. Это — вроде «подключения» имени переменной к чему-то другому. Сначала переменная была «подключена» к "Cows go moo"
, а теперь — к "I am the walrus"
.
message
— это было бы очень плохо. Когда мы объявляем переменную, она оказывается доступной лишь в некоторой части программы. Эта часть называется «областью видимости переменной». Существуют правила, описывающие особенности работы областей видимости. Обычно выявить область видимости переменной можно, выяснив то, в каком блоке, ограниченном фигурными скобками ({}
), она объявлена. Этот блок и можно назвать областью видимости переменной.message = "I am the walrus"
— это приводит к тому, что мы меняем переменную message
так, чтобы она указывала бы на значение "I am the walrus"
. Эту операцию называют присвоением переменной значения, или записью чего-либо в переменную, или установкой переменной.let
, const
и var
. Обычно для объявления переменных лучше всего подходит ключевое слово let
. Если нужно сделать так, чтобы в переменную нельзя было бы записать ничего нового — можно воспользоваться ключевым словом const
. (В некоторых кодовых базах и командах педантично относятся к этому вопросу, заставляя всех, в том случае, если значение записывается в переменную лишь один раз, использовать const
.) Постарайтесь не пользоваться ключевым словом var
, так как с переменными, объявленными с его помощью, связаны запутанные правила, касающиеся определения области видимости переменных.Object
. Тип Object
, сущности, принадлежащие к которому, называют объектами, играет в JavaScript особую роль. Примечательная особенность объектов заключается в том, что они могут быть связаны с другими значениями. Например, объект {flavor: "vanilla"}
имеет свойство flavor
, которое указывает на значение "vanilla"
. Объекты можно воспринимать как самостоятельные значения, из которых тянутся связи к другим значениям.
flavor
), оно указывает на некое значение (вроде "vanilla"
). Но, в отличие от переменной, свойства «живут» внутри самого объекта, а не где-то в коде (в некоей области видимости переменной). Свойство считается частью объекта, а значение, на которое ссылается свойство, частью объекта не считается.{}
или {flavor: "vanilla"}
. В фигурных скобках может быть объявлено множество пар вида свойство: значение
, разделённых запятыми. Это позволяет нам указывать значения, на которые ссылаются свойства объектов.2
равно 2
(другими словами — 2 === 2
), так как мы, где бы ни записали число 2
, «призываем» в это место одно и то же значение. Но каждый раз, когда мы пишем {}
, мы всегда получаем разные значения. Как результат, один объект вида {}
не равен другому объекту, который тоже выглядит как {}
. Попробуйте записать в консоли следующее: {} === {}
(результатом будет false
). Когда компьютер встречает в коде число 2
— он всегда работает с одной и той же двойкой. Но объектные литералы — это уже кое-что другое. Когда компьютер встречает {}
, он создаёт новый объект, который всегда является новым значением. Как же проверять объекты на равенство? Понятие «равенство» можно рассматривать как понятие «идентичность значений». Когда мы говорим: «a
и b
идентичны» — это значит, что мы имеем в виду то, что a
и b
указывают на одно и то же значение (то есть — a === b
). Когда же мы говорим о том, что a
и b
не идентичны, это значит, что a
и b
указывают на различные значения (то есть — a !== b
)..
). Например, если переменная iceCream
указывает на объект, свойство которого flavor
содержит строку "chocolate"
, то конструкция iceCream.flavor
даст нам "chocolate"
.iceCream.flavor
, а иногда — значение свойства iceCream.taste
. Скобочная нотация ([]
) позволяет обращаться к свойствам объектов, задавая их имена с помощью переменных. Например, предположим, что в коде есть такая переменная: let ourProperty = 'flavor'
. Это значит, что конструкция вида iceCream[ourProperty]
даст нам значение "chocolate"
. Что интересно, скобочной нотацией можно пользоваться и при создании объектов: { [ourProperty]: "vanilla" }
.let iceCream = {flavor: "vanilla"}
, позже мы можем его изменить командой iceCream.flavor = "chocolate"
. Обратите внимание на то, что даже если бы мы объявили переменную iceCream
с использованием ключевого слова const
, это, всё равно, не помешало бы нам изменить свойство объекта iceCream.flavor
. Это так из-за того, что использование const
защищает от перезаписи лишь саму переменную iceCream
, а мы меняем свойство (flavor
) объекта, на который ссылается переменная. Некоторые люди отказались от использования const
только из-за того, что это ключевое слово способно ввести программиста в заблуждение.["banana", "chocolate", "vanilla"]
. Использование подобной конструкции приводит к созданию объекта, свойство которого с именем 0
указывает на строку "banana"
, свойство 1
— на строку "chocolate"
, свойство 2
— на значение "vanilla"
. Утомительно было бы записывать то же самое примерно так: {0: ..., 1: ..., 2: ...}
. Поэтому массивы — это полезные структуры. Массивы имеют встроенные механизмы, которые предназначены для работы с их элементами. Среди них — методы map
, filter
и reduce
. Не расстраивайтесь, если имя reduce
кажется вам непонятным. Оно всем кажется непонятным.iceCream.taste
, а в объекте есть только свойство flavor
? Если ответить на этот вопрос, не вдаваясь в детали, то можно сказать, что, попытавшись обратиться к несуществующему свойству, мы получим особое значение undefined
. Если дать на этот вопрос развёрнутый ответ, то начать надо с того, что большинство объектов в JavaScript имеют так называемый «прототип». Прототип объекта можно воспринимать как «скрытое» свойство, которое указывает системе на то, где нужно искать запрашиваемое свойство в том случае, если в самом объекте его нет. В нашем примере, когда оказывается, что в объекте iceCream
нет свойства taste
, JavaScript будет искать это свойство в прототипе объекта, который тоже является объектом. А если и там его не найдёт — то в прототипе прототипа, и так далее. Значение undefined
будет выдано только тогда, когда будет достигнут конец «цепочки прототипов», и при этом свойство .taste
так и не будет найдено. Вам редко придётся напрямую работать с этим механизмом, но, зная о прототипах, можно понять то, почему у объекта iceCream
есть метод toString
, который мы никогда не объявляли. Этот метод берётся из прототипа объекта.sayHi()
, сообщает компьютеру о том, что ему нужно выполнить код, находящийся внутри функции, а потом — вернуться туда, где была вызвана функция. В JavaScript существует множество способов объявления функций, которые немного отличаются друг от друга.
sayHi("Amelie")
. Поведение аргументов в функции похоже на поведение переменных. Слова «параметры» и «аргументы» используют в зависимости от того, о чём именно идёт речь — об объявлении функции, или о её вызове. Однако эта тонкость терминологии важна для тех, кто педантично подходит к программированию, на практике эти термины используются взаимозаменяемо.let message = "I am the walrus"
. Как оказывается, в переменную можно записать и функцию: let sayHi = function() { }
. То, что находится после знака =
, называется функциональным выражением. Оно даёт нам особое значение (функцию), которое представляет собой фрагмент кода. Если нам нужно выполнить этот код — мы можем вызвать соответствующую функцию.let sayHi = function() { }
. Если это так — то тут можно воспользоваться более краткой формой описания функции: function sayHi() { }
. Эта конструкция называется объявлением функции. Вместо того чтобы указывать в левой части выражении имя переменной, мы помещаем это имя после ключевого слова function
. Обычно два вышеописанных стиля создания функций взаимозаменяемы.let
или const
, ниже места её объявления. В случае с функциями это может оказаться неудобным. Функции могут вызывать друг друга. Непростой задачей способно оказаться выяснение того, какая из них должна быть создана первой. Хорошо то, что при использовании объявлений функций (и только при использовании этого метода!), порядок описания функций неважен. Дело в том, что при таком подходе функции «поднимаются» в верхнюю часть области видимости. То есть оказывается, что функции, даже при попытке их вызова из кода, который идёт до их объявления, уже оказываются определёнными и готовыми к работе.this
. Возможно, ключевое слово this
— это концепция JavaScript, которую чаще других понимают неправильно. Это ключевое слово можно сравнить с особым аргументом функции. Но сами мы его функциям не передаём. Его передаёт JavaScript. Значение this
зависит от того, как именно вызывают функцию. Например, при вызове метода объекта с использованием точечной нотации, вроде iceCream.eat()
, this
будет указывать на то, что находится перед точкой. В нашем примере это — объект iceCream
. Значение this
в функции зависит от того, как вызвана функция, а не от того, где она была объявлена. Существуют особые методы, такие, как .bind
, .call
и .apply
, которые дают программисту возможность управлять тем, что попадёт в this
.let sayHi = () => { }
. Они компактны и часто используются для оформления однострочных конструкций. Возможности стрелочных функций ограничены сильнее, чем возможности обычных функций. Например, у них нет ключевого слова this
. Когда в стрелочной функции используют ключевое слово this
— оно берётся из той функции, в которую вложена стрелочная функция. Это похоже на обращение к аргументу или к переменной из функции, вложенной в другую функцию. На практике это означает, что стрелочными функциями пользуются тогда, когда хотят, чтобы в них было бы видно то же значение this
, которое существует в окружающем их коде.this
к функциям. Обычно привязка некоей функции f
к конкретному значению this
и к некоему набору аргументов означает, что создаётся новая функция, которая вызывает функцию f
с этими заранее заданными значениями. В JavaScript есть вспомогательный механизм для привязки функций — метод .bind
, но привязывать this
к функции можно и другими способами. Привязка была популярным способом достижения того, чтобы вложенные функции «видели» бы то же значение this
, что и внешние по отношению к ним функции. Теперь в подобной ситуации используются стрелочные функции, в результате привязка функций используется в наше время нечасто.collectLinks(url)
. Эта функция сначала собирает ссылки, находящиеся на странице какого-то сайта, а потом сама себя вызывает, передавая себе каждую из найденных ссылок. Это происходит до тех пор, пока не будут посещены все страницы некоего сайта. Опасность рекурсии заключается в том, что вполне можно случайно написать функцию, которая будет вызывать саму себя бесконечно. Правда, если в программе и правда оказывается бесконечная рекурсия, это приведёт к переполнению стека вызовов и выполнение программы остановится с ошибкой stack overflow
. Стек переполняется из-за того, что в него попадает слишком много записей о вызванных функциях.setTimeout
принимает коллбэк, который… вызывается после истечения тайм-аута. Но надо отметить, что в функциях обратного вызова нет ничего особенного. Это — обычные функции. И когда мы называем их «функциями обратного вызова», это говорит лишь о том, что мы ожидаем их вызова другими функциями.JavaScript сделан из всех тех концепций, которые мы обсудили. Но состоит этот язык не только из них. Меня очень беспокоили мои знания в области JavaScript. Продолжалось это до тех пор, пока мне не удавалось построить правильную ментальную модель языка. Этим материалом я хочу помочь будущим поколениям разработчиков поскорее понять JavaScript. А вот — мой проект Just JavaScript [2]. Он создан для тех, кто хочет как следует разобраться в том, как работает JavaScript.
Уважаемые читатели! Как вы изучали JavaScript?
Автор: ru_vds
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/341912
Ссылки в тексте:
[1] Image: https://habr.com/ru/company/ruvds/blog/482472/
[2] Just JavaScript: https://justjavascript.com/
[3] Источник: https://habr.com/ru/post/482472/?utm_source=habrahabr&utm_medium=rss&utm_campaign=482472
Нажмите здесь для печати.