JavaScript это фантастический гибкий инструмент веб-дизайна. Но чем больше инструмент, тем больше предстоит о нем узнать.
Вот 12 вопросов (и ответов) о JavaScript, которые, бывает, ставят в тупик даже опытных разработчиков. Некоторые из этих вопросов часто встречаются на собеседованиях.
1. Что такое прототипное наследование и насколько оно полезно?
В JavaScript почти все является объектом. У каждого объекта есть прототип, от которого он наследует значения и поведение. Если объект не содержит запрашиваемое свойство, JS будет искать это свойство в прототипе вместо самого объекта. Он будет подниматься по цепочке прототипов пока не найдет совпадение или вернет ошибку.
Это полезно при создании объектов, разделяющих одинаковые значения и операции. Нахождение их в прототипе позволяет иметь только один их вариант, что делает память проекта эффективной.
var parent = {inherit: true}
var childA = Object.create(parent);
var childB = {};
Object.setPrototypeOf(childB, parent);
class childC extends parent {}
Прототипы могут быть добавлены к объектам при их создании с помощью Object.create() или после – с помощью Object.setPrototypeOf(). В ES2015 есть ключевое слово «class», которое, при использовании с «extends», будет использовать это значение как прототип.
2. Как можно использовать JavaScript для улучшения доступности в Интернете?
Для современных инструментов веб-доступности характерна способность управляться с JavaScript и динамическим контентом. JavaScript может помочь этим технологиям если будет использоваться в качестве улучшения, а не для непосредственной работы.
Удобный способ помочь пользователям с полезным управлением фокусом. Например, в календаре должна быть возможность перемещения по датам с помощью клавиш-стрелок, а клавиши «вверх/вниз» должны пролистывать целую неделю за одно нажатие. Это только один случай прослушивания событий клавиатуры в то время когда фокус находится внутри этого компонента.
Важные изменения данных в результате JavaScript, например, форма обратной связи, также должны быть объявлены для чтения с экрана. Часто это достигается путем маркировки контейнера в качестве live region.
3. Что такое «пузырение» событий и в чем его отличие от захвата событий?
Как захват событий (event capturing) так и пузырение (bubbling) являются процессами под названием «распространение событий» (‘event propagation’), в которых браузеры реагируют на события, происходящие на странице. Более старые браузеры поддерживали только какой-нибудь один из этих методов, но современные поддерживают оба.
Первая фаза – фаза захвата – наступает сразу после того, как событие произошло. Она начинается на самом верхнем уровне (в зависимости от события это или «документ», или «окно»). Оттуда она переходит по <html> и всему, что в нем есть, пока не достигнет элемента, в котором произошло событие.
Вторая фаза – фаза пузырения – происходит позже. Она повторяет процесс, но в обратном порядке, начиная с элемента, где событие было вызвано, и «пузырится» до самого верхнего элемента <html>. При добавлении «прослушивателей» событий (event listeners), это поведение по умолчанию.
4. Как делегирование событий улучшает код на сайтах с большим количеством интерактивных элементов?
Зачастую на сайтах есть множество динамического контента, регулярно обновляющегося на странице. Если эти элементы должны быть интерактивными, потребуется какой-то прослушиватель событий для отслеживания этих взаимодействий. Если для каждого элемента потребуется собственный прослушиватель, это внесет беспорядок в код и увеличит нагрузку на браузер.
Делегирование событий это техника, использующая пузырение событий в качестве своего преимущества. Добавляя прослушиватель к родительскому элементу, разработчики предупреждаются о событиях, касающихся любого из его дочерних элементов.
parentEl.addEventListener('click', function(e) {
if(e.target && e.target.nodeName == 'BUTTON') {
// Button clicked
} });
Внутри обратного вызова события исходной целью этого события всегда будет «цель», которая может быть использована для принятия решения о том, что делать дальше. Например, атрибут данных может содержать идентификатор для ссылки на свойство объекта.
5. Что такое замыкания и как они могут помочь в организации кода?
Функции в JavaScript используют то что называется «лексической средой», имея в виду, что она имеет доступ к переменным, определенным снаружи, в отличие от определенных внутри, которые доступны только изнутри.
function outer() {
let x = 'Web Designer';
function shout() {
alert(`I love ${x}!`);
}
shout(); }
Вызывая outer() мы покажем «I love Web Designer!», но если ссылки «shout» или «х» идут наружу от outer(), оба они останутся неопределенными. А замыкание это комбинация функции с ее лексической средой. В данном случае замыканием является функция «outer».
Замыкания полезны при создании множественных компонентов, так как что-либо, объявленное внутри одного из них, не повлияет на другие. Они могут использоваться для создания частных функций и переменных – в этом проявляется сходство с другими объектно-ориентированными языками вроде Python. В шаблоне модуля замыкания широко используются для обеспечения структурированных способов взаимодействия модулей.
6. Что означает ‘use strict’ вверху блока кода?
ES5 представил возможный вариант JavaScript под названием «строгий режим» (‘strict mode’). В строгом режиме особенности более ранних версий будут вызывать ошибки, а не приводить к незапланированному поведению.
function strictFunction() {
'use strict';
myVar = 4; //ReferenceError }
В этом примере мы обращаемся к несуществующей переменной. Вне строгого режима это добавило бы myVar в глобальную область видимости, что, если мы не будем осторожны, может полностью переписать определенный ранее в этом скрипте функционал. В строгом режиме это выдаст ошибку и предотвратит любые разрушения. Модули ES2015 по умолчанию находятся в строгом режиме, но в замыканиях, созданных с помощью функций, ‘use strict’ можно применить как для целого файла, так и на уровне функции.
7. Что в JavaScript означает термин «hoisting»?
JavaScript уникален тем что не нуждается в компиляции перед распространением. Браузер скомпилирует скрипты как только их найдет и сделает заметки о любых функциях и переменных, которые объявлены внутри них.
Затем браузер делает повторную передачу кода на исполнение, уже зная, где применить эти функции и переменные. Когда блок исполнен, объявления его функций и переменных поднимаются («hoist») вверх блока.
welcome("Matt"); //"Welcome, Matt."
function welcome(name) {
return `Welcome, ${name}.`;
}
В этом примере мы можем использовать функцию ‘welcome’, так как она поднята вверх скрипта.
8. В чем разница между стрелочной и обычной функциями?
ES2015 принес много изменений, и одно из них – стрелочные функции для осуществления быстрого воздействия.
function CountUp() {
this.x = 0;
setInterval(() => console.log(++this.x), 1000); }
var a = new CountUp();
Ключевая разница, помимо краткости в написании, заключается в том что стрелочные функции не создают собственное значение для ‘this’. Вместо этого они используют значения закрывающего блока. В вышеуказанном примере раз в секунду будут выдаваться 1 2, 3 и т. д. С обычной функцией this.x было бы неопределенным и записало бы NaN. Телу стрелочной функции присваивается возвращенное из нее значение. Это делает их полезными для промисов, через которые передаются значения. Обычные, регулярные функции должны явно возвращать значение или то, что оно не определено.
9. Где стоит использовать ключевые слова ‘let’ и ‘const’ вместо ‘var’?
Еще одно важное изменение, пришедшее с ES2015, это представление ‘let’ и ‘const’ в качестве альтернативных способов определения переменных. Переменные, определяемые таким образом, ограничены блоком, в котором они определены. Это обеспечивает большую уверенность в том что значения, созданные внутри разных блоков, не будут пересекаться снаружи.
for(let x=1; x<=3; x++) {
console.log(x); // 1, 2, 3}
console.log(x); // "x is not defined"
Если значение переменной не будет меняться, используйте ‘const’ вместо ‘let’. При попытке переопределить константу будут выдаваться ошибки. Объекты и переменные массива могут меняться внутренне, но не заменяются полностью.
Как ‘let’, так и ‘const’ не «поднимаются» («hoisting»), подобно ‘var’, поэтому на них нельзя ссылаться прежде чем они будут инициализированы. Между стартом блока и инициализацией находится так называемая «временная мертвая зона», которая часто может служить источником путаницы.
10. Что такое функциональное программирование и чем оно отличается?
Это альтернативный способ создания программ путем передачи состояния приложения исключительно через функции. Избегая побочных эффектов, можно разработать легкий для понимания код.
Традиционно проекты JavaScript строятся с объектно-ориентированной структурой. Информация о текущем состоянии программы хранится в объектах, которые обновляются при изменении страницы.
Функциональное программирование несет другой способ мышления. Хотя такие языки, как F#, уже используют его некоторое время, ES2015 привнес в JavaScript важные методы, открывающие его для Интернета.
Все работы должны выполняться внутри «чистых» функций. Это функции, на которые не влияют никакие данные вне области видимости этой функции. Другими словами, если некой функции всегда передаются одни и те же значения, она всегда будет возвращать один и тот же результат.
Это также означает, что между функциями не может быть разделяемого состояния. Любое состояние в приложении, которое необходимо использовать, должно передаваться функции в качестве параметра, а не быть доступным непосредственно.
Наконец, нужно избегать изменения значений в коде после их создания. Например, каждое изменение объекта должно возвращать копию этого объекта с измененным значением. Это делается во избежание побочных эффектов, которые могут привести к ошибкам и усложнить тестирование кода.
11. Как JS может быть использован для улучшения производительности сайта?
В то время, когда большая часть поиска в интернете происходит с телефонов или планшетов, производительность имеет решающее значение. Не у всех есть последние модели устройств, и каждая задержка и заминка могут стоить вам пользователя. К счастью, есть много способов использования JavaScript для недопущения таких ситуаций.
Пассивное состояние
Неравномерная прокрутка – явный признак того, что что-то происходит. В некоторых случаях браузер вынужден ждать, потому что к странице обращаются прослушиватели. Такие события как ‘wheel’ или ‘touchmove’, могут отменить прокрутку. Поэтому странице, прежде чем начнется предусмотренное по умолчанию поведение прокрутки, приходится ждать завершения события. Это может привести к отрывистой и нестабильной прокрутке, что повлечет за собой негативный пользовательский опыт.
document.addEventListener('touchmove', handler, {passive: true});
Чтобы этого избежать, передавайте объект в качестве третьего параметра при добавлении прослушивателя события. Если объект помечен как пассивный, браузер приходит к заключению, что на прокрутку это не повлияет, поэтому она может начаться немедленно.
Этот третий параметр заменяет опцию ‘useCapture’ в старых браузерах, поэтому важно использовать обнаружение функции при использовании этого типа прослушивателя. Для намеренного отключения прокрутки в большинстве браузеров применимо ‘touch-action: none’ в CSS.
Throttling событий
Такие события как прокрутка и изменение размера запускаются как можно быстрее, чтобы любой прослушиватель мог оставаться в курсе происходящего. Если на каждом событии происходит что-то ресурсоемкое, это может быстро привести к остановке страницы.
const resizeDebounce = debounce(() => {
// Code for resize event }, 200);
window.addEventListener('resize', resizeDebounce);
Debouncing – это метод, который регулирует частоту обратных вызовов к одному из таких событий. Реализация функции debounce и то, как часто функция будет вызываться, будет меняться в зависимости от проекта, но, например, сокращение событий до пяти раз в секунду приведет к видимому мгновенному улучшению на странице.
Фокус на окно просмотра
Отслеживание, когда элемент входит в зону видимости на странице, является распространенным использованием события прокрутки. Даже с debouncing вызов getBoundingClientRect() требует от браузера провести повторный анализ макета целой страницы. Есть новый API браузера, под названием IntersectionObserver, который отчитывается о видимости наблюдаемых элементов путем вызова функции при их входе или выходе из окна просмотра. Для сайтов с бесконечной прокруткой это может использоваться в качестве флага для удаления или возвращения предыдущих просмотров.
IntersectionObserver доступен во всех последних версиях браузеров кроме Safari. Стоит попробовать использовать этот API, а затем вернуться к более старым методам, и вы сразу почувствуете разницу.
Разделяйте работу, требующую много ресурсов
Работая с большими наборами данных или обрабатывая большие файлы, такие как изображения, JavaScript может быстро заблокировать окно браузера. Вся работа производится в одном потоке, так что если этот поток занят, интерфейс не может обновляться.
Если вы знаете, что запуск процесса займет много времени, хорошей идеей будет поместить его в web worker. Это скрипты которые запускаются в отдельном потоке, что позволяет интерфейсу пользователя работать более гладко. Скрипты могут говорить друг с другом с помощью специального метода сообщений. У web worker нет доступа к DOM и некоторым свойствам оконного объекта, так что эти сообщения могут использоваться для передачи необходимой информации.
12. Как я могу защитить свой JavaScript-код в будущем?
Один из главных принципов JavaScript состоит в том что он всегда старается избежать разрушительных изменений где только возможно. Подавляющая часть написанного сегодня кода будет функционировать и десять лет спустя, даже с учетом постоянных изменений в этой сфере.
Тем не менее запускаемость блока кода еще не означает, что у него есть будущее. Будет ли ваш код иметь смысл через несколько лет?
Избегайте спагетти-кода
При изначальном создании проекта мысль о том чтобы писать все вместе может быть заманчивой. Хотя это вносит ясность в отношении того, что делает каждый блок кода, это также означает, что поведение кода в целом также сцеплено. Если другая область проекта нуждается в этом функционале, он будет или скопирован или переписан. Если обнаружатся ошибки или будет добавлена новая функция, все версии потребуют индивидуального обновления, а это может занять много времени.
Если код модульный, функционал может подключаться там, где он нужен. Все изменения становятся доступными сразу после написания. Такие техники как шаблон модуля могут быть реализованы без влияния на остальной проект, что облегчает их внедрение.
Будьте независимы от фреймворков
Многие современные фреймворки, такие как React или Polymer, побуждают разработчиков поддерживать модульность путем создания компонентов. Каждый из них имеет свой способ коммуникации с другими частями проекта.
Что случится, когда появится новый, лучший фреймворк? Переключение (или даже обновление) может быть трудоемким заданием. Изыскание новых путей, позволяющих этим старым компонентам работать вместе, также может съесть много времени.
Поэтому для достижения результата старайтесь по возможности использовать собственные особенности и функции JavaScript. Таким образом при смене фреймворков эти точки трения будут минимизированы. Например, используйте объекты для управления манипуляциями с данными прежде чем передавать их компоненту.
Также помогает написание кода на универсальном JavaScript. При условии отказа (где это возможно) от использования API на основе браузеров, код может быть повторно использован как в браузере, так и на сервере в Node.
Делайте уборку
После написания модулей поддерживайте их в чистоте. Тот, кто будет их читать, должен понимать их функционал для ускорения исправления ошибок.
let activeUsers = users.filter(user => user.active === true);
Мощным инструментом для обеспечения будущего вашего кода является самодокументированный код. Использование описательных имен для переменных и итерациях может облегчить чтение кода (по сравнению с именами типа «і»).
Но еще важнее быть последовательным. Придерживайтесь унифицированного стиля, это сделает код более читабельным. Используйте руководство по стилю чтобы определить как должен выглядеть код и используйте инструменты вроде ESLint.
Работайте с масштабом
Требование к читабельности распространяется и на на структуру проекта. Без этого все может очень быстро обратиться в хаос.
Сначала нахождение файлов в одном каталоге все упрощает. Когда скрипты импортируют модули, нет путаницы в том, где они расположены.
По мере развития проектов большие собрания файлов могут потеряться в общей массе. Сохраняйте структуру в хорошо масштабируемом виде, например, сохраняя все модули, которые имеют дело с пользователями, в «пользовательском» каталоге.
Какая структура лучше, зависит от конкретного проекта.
Заботьтесь доступности для тестирования
Фреймворки вроде React побуждают к использованию маленьких компонентов, которые можно использовать повторно. Даже с масштабируемой структурой проекта может быть сложно убедиться, что все они по-прежнему работают правильно. Благодаря написанию тестов проекты могут быть развернуты с уверенностью.
Unit-тесты будут работать на уровне модулей. Такие инструменты как Mocha и Jest позволяют разработчикам определять ожидаемый вывод для данного ввода. Периодический запуск этих тестов поможет убедиться в отсутствии побочных эффектов.
При написании модулей следует предусмотреть возможность их отдельного тестирования. Это означает наличие как можно меньшего количества зависимостей и отсутствие расчета на глобальное состояние.
Этим проверка проектов не ограничивается. Есть еще интегральное и функциональное тестирование. Для длительной будущей работоспособности кода он должен как можно больше покрываться тестами.
Язык будущего
Но наилучший способ обеспечить будущность своего кода – писать его в синтаксисе будущего. Это может звучать как шаг в неизвестность, но есть инструменты, максимально облегчающие этот процесс.
Babel это транспилер, то есть инструмент, который конвертирует одну форму JavaScript в другую. Он используется для перевода современного кода в форматы, которые могут понимать старые браузеры и окружения.
ES2015 привнес в JavaScript много такого, что может помочь в написании чистого кода, например стрелочные функции, промисы и собственные модули. Последний стандарт – ES2017 – обеспечивает еще большее удобство благодаря асинхронным функциям. С правильными предустановками, Babel может помочь конвертировать все это в код для сегодняшнего использования.
Со временем проекты смогут вообще пропускать шаг транспиляции. Но на данный момент код должен поддерживаться в состоянии, обеспечивающем его использование в будущем.
***
Подписывайтесь на наш канал в Telegram!
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]