От автора: эти вопросы по JavaScript ставят в тупик даже опытных разработчиков. JS – удивительный и гибкий инструмент веб-дизайна. Но даже в самых больших инструментах всегда есть чему учиться. Ниже представлены 12 вопросов (и ответов) по JS, которые ставят в тупик множество разработчиков, даже опытных JS программистов.
На первой странице мы рассмотрим основные вопросы и ответы, в том числе, которые часто попадались на собеседованиях. На второй странице мы более подробно разберем две более сложные области: как с помощью JS улучшить производительность сайта и как обезопасить код в долгосрочной перспективе.
1. Что такое прототипное наследование и в чем его польза?
В JS почти все представляет собой объект. У каждого объекта есть прототип, от которого он наследует значения и поведение. Если объект не включает запрашиваемое свойство, JS произведет его поиск внутри прототипа. Поиск продолжается по цепочке прототипов, пока не будет найдено свойство. В противном случае вернется ошибка.
Полезная функция при создании объектов, которые делят значения и операции. Они живут в прототипе, поэтому требуется только одна копия, что улучшает использование памяти в проектах.
1 2 3 4 5 |
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. Как с помощью JS можно улучшить доступность?
Современные инструменты доступности способны обрабатывать JS и динамический контент. Их можно использовать в качестве помощников этим технологиям, пока они используются, как улучшения, а не требуются для работы.
Распространенный способ помочь пользователям – это обеспечить полезное управление фокусом. Календарь, например, должен переключаться между днями при помощи стрелок, а клавиши вверх и вниз должны переключать недели. Для этого необходимо просто следить за событиями клавиатуры, пока фокус находится внутри этого компонента.
Изменение важных данных с помощью JS (например, обратная связь) должно анонсироваться в скрин ридерах. Зачастую такое поведение достигается путем пометки контейнера, как живой области.
3. Что такое всплытие события, и чем оно отличается от перехвата события?
Делегирование событий – техника, использующая всплытие событий в своих интересах. Добавив обработчик на родительский элемент, разработчики будут знать о срабатывании событий любого дочернего элемента.
Перехват события и всплытие события – части процесса «распространение событий», в котором браузеры отвечают на срабатывания событий на странице. Старые браузеры умеют либо то, либо другое. Сейчас же браузеры умеют и то и то.
Первый этап – этап перехвата – возникает, как только срабатывает событие. Он начинается на самом верхнем уровне (т.е. document или window в зависимости от события). Сверху он идет вниз через и остальные теги и достигает элемента, внутри которого возникло событие.
Следом запускается второй этап – этап всплытия. Он повторяет процесс, но уже в обратном порядке, начиная с элемента, на котором произошло событие, и «всплывая» до элемента верхнего уровня html. При добавлении обработчиков событий такое поведение стоит по умолчанию.
4. Как делегирование событий улучшает код на сайтах с большим количеством интерактивных элементов?
На веб-сайтах зачастую масса динамического контента, который постоянно меняется. Если эти элементы должны быть интерактивными, вам потребуются некого рода обработчики событий, подхватывающие эти взаимодействия. Если на каждый элемент создавать свой обработчик события, это загромоздит код и увеличит количество элементов, за которыми браузер обязан следить.
Делегирование событий – техника, использующая всплытие событий в свою пользу. Добавив обработчик к родителю, разработчики получают оповещения о событиях на его дочерних элементах.
1 2 3 4 |
parentEl.addEventListener('click', function(e) { if(e.target && e.target.nodeName == 'BUTTON') { // Button clicked } }); |
Внутри колбек-функции события оригинальная цель всегда будет target. С ее помощью можно решать, что делать далее. Например, data-атрибут может хранить ID, чтобы ссылаться на свойство объекта.
5. Что такое замыкания и чем они полезны в организации кода?
Функции в JS используют так называемую «лексическую среду», т.е. у них есть доступ к переменным, определенным извне, но доступ к переменным, заданным внутри, можно получить только изнутри.
1 2 3 4 5 6 |
function outer() { let x = 'Web Designer'; function shout() { alert(`I love ${x}!`); } shout(); } |
Вызов outer() вернет «I love Web Designer!», но если ссылаться на shout или x не из outer(), и переменная и функция будут не определены. Замыкание представляет собой функцию вместе с ее лексической средой. В нашем случае замыканием будет функция outer.
Замыкания полезны при создании нескольких компонентов, так как что-либо, объявленное внутри, не повлияет на другой код. С их помощью можно создавать приватные функции и переменные, как в ООП языках типа Python. Шаблон модуля активно использует замыкания для создания структурированных способов взаимодействия с модулями.
6. Что значит «use strict» перед блоком кода?
ES5 представил необязательный вариант SJ под названием strict mode. В строгом режиме причуды более ранних версий будут выбрасывать ошибки вместо непредсказуемого поведения.
1 2 3 |
function strictFunction() { 'use strict'; myVar = 4; //ReferenceError } |
Выше мы получаем доступ к несуществующей переменной. В нестрогом режиме myVar будет добавлена в глобальное пространство видимости, что при неаккуратном использовании может переписать ранее определенную функциональность скрипта. В строгом режиме вылетит ошибка, предотвращающая любые разрушения. Модули ES2015 находятся в строгом режиме по умолчанию, однако в замыканиях, созданных с помощью функций, use strict может применяться на функциональном уровне, а также ко всему файлу.
7. Что значит термин hoisting по отношению к JS?
JS уникален в том плане, что его не нужно компилировать перед распределением. Браузер скомпилирует скрипты как только найдет их и сделает записи о любой функции и переменной, объявленной внутри.
Затем браузер выполняет код, зная, где эти функции и переменные применить. Как только блок выполнен, его функции и переменные «поднимаются» в верхнюю часть блока.
1 2 3 4 |
welcome("Matt"); //"Welcome, Matt." function welcome(name) { return `Welcome, ${name}.`; } |
В этом примере мы можем использовать функцию welcome, так как она поднимается в верхнюю часть скрипта.
8. В чем разница между стрелочной и обычной функцией?
ES2015 представил множество изменений, и одно из них – быстрое влияние на стрелочные функции.
1 2 3 4 |
function CountUp() { this.x = 0; setInterval(() => console.log(++this.x), 1000); } var a = new CountUp(); |
Ключевое отличие, помимо более краткой формы записи, в том, что стрелочные функции не создают свое значение для this. Вместо этого они используют значение замыкающего блока. В пример выше в лог выведется 1, 2, 3 и т.д. каждую секунду. В обычной функции this.x будет undefined, и в лог выведется NaN. Тело стрелочной функции будет возвращаемым значением. Это делает стрелочные функции полезными для промисов, через которые передаются значения. Обычные функции обязаны явно возвращать значение, в противном случае вернется undefined.
9. Где нужно использовать ключевые слова let и const вместо var?
Еще одно фундаментальное изменение в ES2015 – появились let и const – альтернативные способы создания переменных. Объявленные таким способом переменные ограничены блоком, в котором объявлены. Это делает более четкое разделение между блоками (код разных блоков не будет влиять друг на друга).
1 2 3 |
for(let x=1; x<=3; x++) { console.log(x); // 1, 2, 3} console.log(x); // "x is not defined" |
Если значение переменной не будет меняться, используйте const вместо let. При попытке переопределить константу будет выброшена ошибка. Объекты и массивы все еще можно изменять внутри, но полностью они не заменяются.
Let и const не «поднимаются» как var, т.е. на них нельзя ссылаться перед инициализацией. Область между началом блока и инициализацией называется «временная мертвая зона». Зачастую она может вводить в ступор.
10. Что такое функциональное программирование, и в чем его отличие?
Это альтернативный способ создания программ путем передачи состояния приложения исключительно через функции. Избегая побочных эффектов, можно разработать легко понимаемый код.
Обычно, JS проекты построены по структуре ООП. Информация о текущем состоянии программы хранится внутри объектов, которые обновляются с изменением страницы.
Функциональное программирование дает другой способ мышления. Языки типа F# пользуются данным подходом уже довольно давно, однако ES2015 принес важные методы в JS и открыл их вебу.
Вся работа выполняется внутри «чистых» функций. Это функции, не затрагиваемые данными любого рода снаружи области видимости функции. Другими словами, если передавать в функцию одни и те же значения, возвращаться будет всегда одинаковый результат.
Это так же означает, что между такими функциями не может быть общих состояний. Любое состояние внутри приложения, которое необходимо использовать, не должно вызываться напрямую и должно быть передано в функцию в виде параметра.
Код должен избегать изменения значений после их получения. Например, каждое изменение объекта должно возвращать копию объекта с измененным значением. Это делается во избежание побочных эффектов, которые могут вызвать баги и усложнить тестирование кода.
11. Как с помощью JS улучшить производительность сайта?
Во времена, когда большая часть веба просматривается через мобильные устройства и планшеты, производительность играет критическое значение. Не у всех новейшее устройство, и каждая задержка и подергивание могут стоить вам покупателя. К счастью, существует множество способов на JS, как этого избежать.
Сохраняем пассивность
Чрезмерная прокрутка – явный признак того, что что-то происходит. В некоторых случаях браузер вынужден ждать, так как обработчики обращаются к странице. События типа wheel и touchmove способны отменять прокрутку, поэтому страница должна ждать, пока событие не завершится до начала прокрутки.
Это вызывает дерганое и нестандартное поведение при прокрутке, что выливается в плохой UX.
1 |
document.addEventListener('touchmove', handler, {passive: true}); |
Чтобы избежать этого, передайте объект в качестве третьего параметра при добавлении обработчика события. Пометив событие пассивным, браузер думает, что прокрутка ничему не помешает, поэтому ее можно начать сразу же.
Третий параметр заменяет опцию useCapture в старых браузерах, поэтому важно использовать обнаружение функций при использовании обработчиков данного типа. Для преднамеренного отключения прокрутки в большинстве браузеров поможет запись в CSS touch-action: none.
События проверки
События типа прокрутки и изменения размера запускаются максимально быстро, чтобы обработчик всегда обновлялся. Если на каждом событии происходит что-то ресурсоемкое, это может быстро заморозить страницу.
1 2 3 |
const resizeDebounce = debounce(() => { // Code for resize event }, 200); window.addEventListener('resize', resizeDebounce); |
Debouncing – техника, проверяющая как часто на одно из событий вызывается колбек. Реализация функции debounce и того, как часто вызывается функция, будет отличаться от проекта к проекту, но уменьшение событий до 5 в секунду, например, сразу же улучшить производительность страницы.
Фокус на вьюпорте
Обычно событие scroll используется для определения момента попадания элемента в поле зрения страницы. Даже с debouncing вызов getBoundingClientRect требует от браузера повторного анализа макета всей страницы. Есть новый браузерный API IntersectionObserver, который говорит о видимости элемента, вызывая функцию, когда элемент попадает или исчезает с вьюпорта. Для сайтов с бесконечным скролом это можно использовать как флаг для удаления или обновления старого представления.
IntersectionObserver доступен во всех последних браузерах кроме Safari. Стоит использовать этот API и фолбеком старых техники, так как разница сильно заметна.
Отделяйте затратную работу
При работе с большими объемами данных или обработке больших файлов типа изображений JS может быстро заморозить окно браузера. Вся работа выполняется на одной ветке. Если ветка занята, интерфейс нельзя обновить.
Если вы знаете, что процесс займет много времени, его хорошо бы поместить в веб-воркер. Эти скрипты запускаются в отдельной ветке, которая не затрагивает работу интерфейса. Скрипты могут общаться друг с другом через специальный метод сообщений. Веб-воркеры не имеют доступа к DOM и некоторым свойствам объекта window. С помощью этих сообщений можно передавать необходимую информацию.
12. Как я могу обезопасить свой JS код в долгосрочной перспективе?
Один из базовых принципов JS – JS всегда старается избегать ломающих изменений там, где это возможно. Большая часть написанного сегодня кода будет работать и десять лет спустя, несмотря на постоянно меняющуюся индустрию.
Если блок кода запускается, это не говорит о том, что он будет запускаться в будущем. Будет ли ваш код работать через пару лет?
Избегайте спагетти кода
В первом проекте у вас может возникнуть желание писать все в одном месте. Так явно видно, что делает каждый блок кода, но это также связывает все поведение. Если в другой области проекта понадобится этот функционал, его придется копировать или переписывать. Если будет найден баг, или добавлена новая функция, необходимо будет обновлять все версии, что может отнять много времени.
Сохраняя модульность кода, вы можете подключать функциональность там, где она необходима. После написания сразу доступны любые изменения. Техники типа модульного шаблона могут быть реализованы без влияния на остальной проект, что облегчает их создание.
Не используйте фреймворки
Множество современных фреймворков типа React или Polymer советуют разработчикам хранить все в модулях через создание компонентов. Каждый компонент может по-своему общаться с другими частями проекта.
Что делать, когда выйдет следующий лучший фреймворк? Переходить на него или даже просто обновление может быть довольно сложной задачей. Поиск новых способов оживить старые компоненты может съесть много времени.
По возможности используйте стандартные JS функции. Таким образом, при изменении фреймворков вы минимизируете риски. Например, используйте объекты для обработки манипуляций с данными перед их передачей в компонент.
Это также помогает при написании универсального JS. По возможности не используйте браузерные API, так код можно будет повторно использовать как в браузере, так и на сервере в Node.
Убирайте
Как только модули написаны поддерживайте в них чистоту. Любой читающий должен понимать функциональность для ускорения дебага.
1 |
let activeUsers = users.filter(user => user.active === true); |
Самодокументирующий код – мощный инструмент защиты кода на будущее. Описательные имена в переменных итераторов, например, упростят чтение, в отличие от стандартного i.
Самое главное, будьте последовательны. Придерживаясь одного стиля кода, вы придаете ему читаемости. Определяйте общий вид кода с помощью стилевых гидов и применяйте его с помощью ESLint.
Работайте на масштаб
Читаемость также распространяется на структуру проекта. Без нее все быстро запутается.
При работе над первым проектом хранение всех файлов в одной папке упрощает работу. Когда скрипты импортируют модули, вам сразу ясно, где они расположены.
С разрастанием проекта большие коллекции файлов могут потеряться в массе. Поддерживайте хорошо масштабируемую структуру. Храните все модули, работающие с пользователями, папке users, например. Конечно же, идеальная структура зависит от проекта. Для одностраничного приложения хранение модели, представления и контроллера отдельно – обязательное требование.
Поддерживайте тестируемость
Фреймворки типа React советуют создавать маленькие, повторно используемые компоненты. Даже в масштабируемой структуре проекта может быть сложно проверить их работу. Написав тесты, проект можно разворачивать с уверенностью.
Юнит тесты будут работать на модульном уровне. Инструменты типа Mocha и Jest позволяют разработчикам определять ожидаемый вывод для заданного ввода. Периодически запускайте эти тесты, чтобы избежать сторонних эффектов.
Модули должны писаться так, чтобы их можно было тестировать изолированно. То есть нужно как можно меньше внешних зависимостей, и нельзя полагаться на глобальное состояние.
Также есть интеграционное и функциональное тестирование. Оно должно максимально покрывать приложение, чтобы код долго работал.
Язык завтрашнего дня
Лучший способ обезопасить код – писать его на синтаксисе будущего. Это может показаться шагом в неизведанное, однако есть инструменты максимально упрощающие этот процесс.
Babel – транспайлер, инструмент, конвертирующий одну форму JS в другую. Его используют для превращения современного кода в форматы для старых браузеров и окружения.
ES2015 много добавила в JS относительно чистоты кода. Например, стрелочные функции, промисы и нативные модули. Последний стандарт ES2017 принесет еще больше удобств, благодаря асинхронным функциям. Babek умеет конвертировать весь этот код, чтобы использовать его уже сегодня.
В конце концов, проекты смогут вообще пропускать шаг транспиляции. Но сейчас он необходим для защиты кода.
Автор: Matt Crouch
Источник: //www.creativebloq.com/
Редакция: Команда webformyself.