- PVSM.RU - https://www.pvsm.ru -
Всем привет! В конце сентября в OTUS стартует новый поток курса «Fullstack разработчик JavaScript» [1]. В преддверии начала занятий хотим поделиться с вами авторской статьей, подготовленной специально для студентов курса.
Автор статьи: Павел Якупов

Превью. Хочу сразу отметить, что в данной статье разбираются темы, хорошо знакомые «ниндзям», и больше статья нацелена на то, чтобы новички лучше поняли некоторые нюансы языка, и могли не потеряться в задачах, которые часто дают на собеседовании — ведь на самом деле подобные таски никакого отношения к реальной разработке не имеют, а те, кто их дают, чаще всего таким способом пытаются понять, насколько хорошо вы знаете JavaScript.
Как именно данные хранятся в JavaScript? Многие курсы обучения программированию начинают объяснения с классического: переменная это некая «коробка» в которой у нас хранятся какие-то данные. Какие именно, для языков с динамической типизацией вроде как оказывается неважно: интерпретатор сам «проглотит» любые типы данных и динамически поменяет тип, если надо, и задумываться над типами переменных, и как они обрабатываются, не стоит. Что конечно, неправильно, и поэтому мы начнем сегодняшнее обсуждение с особенностей, которые часто ускользают: как сохраняются переменные в JavaScript — в виде примитивов(копий) или в виде ссылок.
Сразу перечислим виды переменных, которые могут храниться в виде примитивов: это boolean, null, undefined, Number, String, Symbol, BigInt. Когда мы встречаем отдельно объявленные переменные с данным типом данных, мы должны помнить, что во время первичной инициализации они создают ячейку памяти — и что они могут присваиваться, копироваться, передаваться и возвращаться по значению.
В остальном JavaScript опирается на ссылочные области памяти. Зачем они нужны? Создатели языка старались создать язык, в котором память использовалась бы максимально экономно(и это было совершенно не ново на тот момент). Для иллюстрации, представьте, что вам нужно запомнить имена трех новых коллег по работе — совершенно новые имена, и для усиления сравнения, ваши новые коллеги из Индии или Китая с необычными для вас именами. А теперь представьте, что коллег зовут также, как вас, и двух ваших лучших друзей в школе. В какой ситуации вам будет запомнить легче? Здесь память человека и компьютера работает схоже. Приведем несколько конкретных примеров:
let x = 15; //создаем переменную x
x = 17;// произошла перезапись
console.log(x)// тут все понятно
//и маленькая задачка с собеседований
let obj = {x:1, y:2} // создаем объект
let obj1 = obj; // присвоем obj к obj1
obj1.x = 2; // поменяем значение у "младшего"
console.log(obj1.x); // тут понятно, только присвоили
console.log(obj.x) // и чему же сейчас равен obj.x ?
Таким образом, если вы встретите подобную задачу на собеседовании, постарайтесь сразу понять, какой перед вами тип данных — откуда и как он получил значение, как примитивный тип, или как ссылочный.

Для того, чтобы понять, как именно работает контекст в JS, нужно изучить несколько пунктов:
Давным-давно, еще в ES5 все было достаточно просто: было только объявление переменной с помощью var, которое при объявлении в потоке выполнения программы считалось глобальным (что означало, что переменная приписывается как свойство к глобальному объекту, такому как window или global). Далее на сцену пожаловали let и const, которые ведут себя несколько по другому: к глобальному объекту они не приписываются, и в памяти сохраняются по другому, ориентируясь на блочную область видимости. Сейчас уже var считается устаревшим, потому как его использование может привести к засорению глобальной области видимости, и кроме того, let выглядит куда более предсказуемо.
1. Итак, для понимания стоит твердо уяснить что такое области видимости в JavaScript(scope). Если переменная объявлена в глобальной области видимости с помощью директивы let, тогда она не приписывается к объекту window, но сохраняется глобально.
Перейдем к задачам, которые чаще всего получают новички по вопросам контекста на собеседовании.
//задание: что же выведется в консоль?
let x = 15;
function foo(){
let x = 13;
return x;
}
console.log(x)// 15 из глобальной области видимости
foo();
console.log(x)// ответ все тот же
x = foo();
console.log(x)// а вот сейчас return поменял наше переменную, вернув другое значение
2. В тоже время не все новички в курсе, как интерпретатор JavaScript считывает код: на самом деле он читает его два раза, в первый раз он считывает код функций, объявленных как Function Declaration(и готов их выполнить при втором, настоящем считывании и выполнении). Ещё один маленький фокус связан с var и let: при первом чтении переменной с директивой var присваивается значение undefined. А вот с let её преждевременный вызов вообще невозможен:
console.log(x);
console.log(y)
var x = 42;
let y = 38;
//что будет в консоли?
// а будет undefined и error!
3. Стрелочные функции, которые появились в ES6, достаточно быстро завоевали популярность — их очень быстро взяли на вооружение программисты на Node.js (за счет быстрого обновления движка) и React (из-за особенностей библиотеки и неизбежного использования Babel). В отношении контекста стрелочные функции соблюдают следующее правило: они не привязываются к this. Проиллюстрируем это:
var x = 4;
var y = 4;
function mult(){
return this.x * this.y;
}
let foo = mult.bind(this);
console.log(foo());
let muliply = ()=>x*y;
console.log(muliply());
/* стрелочная функция здесь выглядит куда лаконичнее и логичнее
если бы x и y были инициализированы через литерал let, то function declaration вообще бы не сработал таким способом */

Сразу скажем: массив по сути является объектом и в JavaScript это не первая вариация объекта — Map, WeakSet, Set и коллекции тому подтверждение.
Итак, массив является объектом, а его отличие от обычного объекта в JS, заключается в первую очередь в большей скорости работы за счет оптимизации индексации, а во-вторых в наследовании от Array.prototype, которые предоставляет бoльший набор методов, чего его «старший брат» Object.prototype.
console.log(typeof({}))
console.log(typeof([]))
console.log(typeof(new Set))
console.log(typeof(new Map))
//и все это будет один и тот тип объекта
Далее на очереди странностей в типах данных идет null. Если спросить у JavaScript, к какому типу данных относится null, то мы получим достаточно однозначный ответ. Однако и здесь не обойдется без некоторых фокусов:
let x = null;
console.log(typeof(x));
//Отлично! Следовательно, null происходит от objet, логично?
console.log(x instanceof Object.prototype.constructor); //false
//А вот и нет! Видимо это просто придется просто запомнить)
Стоит запомнить, что null является специальным типом данных — хотя начало предыдущего примера и указывало строго на другое. Для лучшего понимания, зачем именно данный тип был добавлен в язык, мне кажется, стоит изучить основы синтаксиса C++ или С#.
И конечно, на собеседованиях часто попадается такая задача, чья особенность связана с динамической типизацией:
console.log(null==undefined);//true
console.log(null===undefined);// а вот тут уже false
С приведением типов при сравнении в JS связано большое количество фокусов, всем мы их здесь привести физически не сможем. Рекомендуем обратиться к «Что за черт JavaScript „ [2].

Сложение строк. На самом деле сложение строк с числами нельзя отнести к ошибкам в разработке языка, однако в контексте JavaScript это привело к известным примерам, которые считаются недостаточно логичными:
codepen.io/pen/?editors=0011 [3]
let x = 15;
let y = "15";
console.log(x+y);//здесь происходит "склеивание"
console.log(x-y); // а здесь у нас происходит нормальное вычитание
То, что плюс просто складывает строки с числами — относительно нелогично, но это нужно просто запомнить. Особенно непривычно это может быть потому, что другие два интерпретируемых языка, которые популярны и широко используются и в веб-разработке — PHP и Python — подобных фокусов со сложением строк и чисел не выкидывают и ведут себя куда более предсказуемо в подобных операциях.
Менее известны подобные примеры, например c NaN:
console.log(NaN == NaN); //false
console.log(NaN > NaN); //false
console.log(NaN < NaN); //false … ничего не сходится... стоп, а какой тип данных у NaN?
console.log(typeof(NaN)); // number
Часто NaN приносит неприятные неожиданности, если вы, например, неправильно настроили проверку на тип.
Куда более известен пример с 0.1 +0.2 — потому как эта ошибка связана с форматом IEEE 754, который используется также, к примеру, в столь “математичном» Python.
Так же включим менее известный баг с числом Epsilon, причина которого лежит в том же русле:
console.log(0.1+0.2)// 0.30000000000000004
console.log(Number.EPSILON);// 2.220446049250313e-16
console.log(Number.EPSILON + 2.1) // 2.1000000000000005
КОМММЕНТАРИЙ АВТОРА ПОТОМ УДАЛИТЬ: здесь могла быть картинка с эйнштейном: https://cs11.pikabu.ru/post_img/2019/02/07/6/1549532414127869234.jpg
И вопросы, которые несколько сложнее:
Object.prototype.toString.call([])// эта конструкция вообще сработает?
// -> вернет '[object Array]'
Object.prototype.toString.call(new Date) // сработает ли это с Date?
// -> '[object Date]' да тоже самое

Многим новичкам непонятны браузерные события. Часто даже незнакомы самые основные принципы, по которым работают браузерные события — перехват, всплытие и события по умолчанию. Самая загадочная с точки зрения новичка вещь — это всплытие события, который, вне сомнения, обосновано в начале вызывает вопросы. Всплытие работает следующим образом: когда вы кликаете по вложенному DOM — элементу, событие срабатывает не только на нем, но и на родителе, если на родителе также был установлен обработчик с таким событием.
В случае, если у нас происходит всплытие события, нам может понадобится его отмена.
//недопущение смены цвета всех элементов, которые находятся выше по иерархии
function MouseOn(e){
this.style.color = "red";
e.stopPropagation(); // вот тут остановочка
}
Кроме того, для новичков часто представляет проблему отмена событий, которые происходят по умолчанию. В особенности это важно при разработке форм — потому как валидацию формы, к примеру, нужно проводить как на стороне клиента, так и на стороне сервера:
codepen.io/isakura313/pen/GRKMdaR?editors=0010 [4]
document.querySelector(".button-form").addEventListener(
'click', function(e){
e.preventDefault();
console.log('отправка формы должна быть остановлена. Например, для валидации')
}
)
Отмена всплытия события может нести в себе и некоторые неприятности: к примеру, вы можете создать так называемую «мертвую зону», в которой не сработает необходимая вещь — к примеру, событие элемента, которому «не посчастливилось» оказаться рядом.
На этом все. Ждём вас на бесплатном вебинаре [1], который пройдет уже 12 сентября.
Автор: Дмитрий
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/329599
Ссылки в тексте:
[1] «Fullstack разработчик JavaScript»: https://otus.pw/Q0Ws/
[2] «Что за черт JavaScript „: https://habr.com/ru/company/mailru/blog/335292/
[3] codepen.io/pen/?editors=0011: https://codepen.io/pen/?editors=0011
[4] codepen.io/isakura313/pen/GRKMdaR?editors=0010: https://codepen.io/isakura313/pen/GRKMdaR?editors=0010
[5] learn.javascript.ru: https://learn.javascript.ru/
[6] developer.mozilla.org/ru/docs/Web/JavaScript/Reference: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference
[7] learn.javascript.ru/event-bubbling: https://learn.javascript.ru/event-bubbling
[8] learn.javascript.ru/bind#reshenie-2-privyazat-kontekst-s-pomoschyu-bind: https://learn.javascript.ru/bind#reshenie-2-privyazat-kontekst-s-pomoschyu-bind
[9] medium.com/@KucherDev/когда-и-почему-стоит-использовать-стрелочные-функции-es6-3135a973490b: https://medium.com/@KucherDev/%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0-%D0%B8-%D0%BF%D0%BE%D1%87%D0%B5%D0%BC%D1%83-%D1%81%D1%82%D0%BE%D0%B8%D1%82-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C-%D1%81%D1%82%D1%80%D0%B5%D0%BB%D0%BE%D1%87%D0%BD%D1%8B%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8-es6-3135a973490b
[10] github.com/denysdovhan/wtfjs/blob/master/README.md: https://github.com/denysdovhan/wtfjs/blob/master/README.md
[11] habr.com/ru/company/otus/blog/456124: https://habr.com/ru/company/otus/blog/456124/
[12] habr.com/ru/company/otus/blog/457616: https://habr.com/ru/company/otus/blog/457616/
[13] habr.com/ru/company/otus/blog/456724: https://habr.com/ru/company/otus/blog/456724/
[14] Источник: https://habr.com/ru/post/466873/?utm_source=habrahabr&utm_medium=rss&utm_campaign=466873
Нажмите здесь для печати.