JavaScript

Объясните делегирование событий

Делегирование событий — это метод, который заключается в добавлении слушателей событий к родительскому элементу вместо добавления их к дочерним элементам. Слушатель будет срабатывать всякий раз, когда событие запускается на дочерних элементах из-за всплытия события в DOM. Преимущества этого метода:

  • Уменьшается потребление памяти, потому что на родительском элементе требуется только один обработчик, вместо того чтобы прикреплять обработчики событий к каждому потомку.
  • Нет необходимости отвязывать обработчик от удаляемых элементов и привязывать событие для новых элементов.

Объясните, как работает `this` в JavaScript

Для this нет простого объяснения; это одно из самых запутанных понятий в JavaScript. Упрощенное объяснение заключается в том, что значение this зависит от того, как вызывается функция. Я прочитал много объяснений this в Интернете, и объяснение [Арнава Аггравала] показалось мне самым ясным. Применяются следующие правила:

  1. Если при вызове функции используется ключевое слово new, this внутри функции является совершенно новым объектом.
  2. Если apply, call или bind используются для вызова/создания функции, this внутри функции — это объект, переданный в качестве аргумента.
  3. Если функция вызывается как метод, например obj.method(), — this — это объект, свойством которого является функция.
  4. Если функция вызывается как свободный вызов функции, то есть она была вызвана без каких-либо из вышеуказанных условий, this — это глобальный объект. В браузере это объект window. Если в строгом режиме ('use strict'), this будет undefined вместо глобального объекта.
  5. Если применяется несколько вышеуказанных правил, выигрывает и устанавливает значение this правило, которое находится выше.
  6. Если функция является стрелочной функцией ES2015, она игнорирует все вышеуказанные правила и получает значение this своего окружающего контекста в момент ее создания.

Для подробного объяснения ознакомьтесь с его [статьей на Medium].

Можете ли вы привести пример того, как работа с this изменилась в ES6?

ES6 позволяет использовать [стрелочные функции], которые используют [охватывающую лексическую область видимости]. Это обычно удобно, но не позволяет вызывающему коду контролировать контекст через .call или .apply — следствием чего является то, что такая библиотека, как jQuery, не будет правильно привязывать this в функциях обработчика событий. Поэтому важно помнить об этом при рефакторинге больших устаревших приложений.

Объясните, как работает прототипное наследование

Все объекты JavaScript имеют свойство __proto__, за исключением объектов, созданных с помощью Object.create(null), которое является ссылкой на другой объект, называемый «прототипом» объекта. Когда свойство доступно для объекта и если свойство не найдено в этом объекте, движок JavaScript смотрит на __proto__ объекта, и __proto__ __proto__ и так далее, пока не найдет свойство, определенное в одном из __proto__, или пока не достигнет конца цепочки прототипов. Такое поведение имитирует классическое наследование, но на самом деле это скорее [делегирование, чем наследование].

Пример прототипного наследования

// Конструктор родительского объекта. function Animal(name) {   this.name = name; } // Добавляем метод в прототип родительского объекта. Animal.prototype.makeSound = function () {   console.log('The ' + this.constructor.name + ' makes a sound.'); }; // Конструктор дочернего объекта. function Dog(name) {   Animal.call(this, name); // Вызов родительского конструктора. } // Устанавливаем прототип дочернего объекта в качестве прототипа родительского. Object.setPrototypeOf(Dog.prototype, Animal.prototype); // Добавляем метод в прототип дочернего объекта. Dog.prototype.bark = function () {   console.log('Woof!'); }; // Создаем новый экземпляр Dog. const bolt = new Dog('Bolt'); // Вызываем методы на дочернем объекте. console.log(bolt.name); // "Bolt" bolt.makeSound(); // "The Dog makes a sound." bolt.bark(); // "Woof!"

Стоит отметить следующее:

  • .makeSound не определен в Dog, поэтому движок поднимается по цепочке прототипов и находит .makeSound в унаследованном Animal.
  • Использование Object.create для построения цепочки наследования больше не рекомендуется. Вместо этого используйте Object.setPrototypeOf.

Что вы думаете о AMD vs CommonJS?

Оба являются способами реализации модульной системы, которая не была изначально присуща JavaScript до появления ES2015. CommonJS является синхронным, в то время как AMD (Asynchronous Module Definition) очевидно асинхронным. CommonJS разработан с учетом серверной разработки, в то время как AMD, с его поддержкой асинхронной загрузки модулей, больше предназначен для браузеров.

Я нахожу синтаксис AMD довольно многословным, а CommonJS ближе к стилю написания операторов импорта в других языках. В большинстве случаев я нахожу AMD ненужным, потому что если вы загружали весь свой JavaScript в один объединенный файл, вы не получили бы выгоды от асинхронных свойств загрузки. Кроме того, синтаксис CommonJS ближе к стилю написания модулей Node, и меньше накладных расходов на переключение контекста при переключении между клиентской и серверной разработкой на JavaScript.

Я рад, что с модулями ES2015, которые поддерживают как синхронную, так и асинхронную загрузку, мы наконец-то можем придерживаться одного подхода. Хотя он еще не полностью развернут в браузерах и в Node, мы всегда можем использовать транспиляторы для преобразования нашего кода.

Объясните, почему следующее не работает как IIFE: `function foo(){ }();`. Что нужно изменить, чтобы правильно сделать это IIFE?

IIFE расшифровывается как Immediately Invoked Function Expressions (немедленно вызываемые функциональные выражения). Парсер JavaScript читает function foo(){ }(); как function foo(){ } и ();, где первое — это объявление функции, а второе (пара скобок) — попытка вызова функции, но без указания имени, поэтому выдается ошибка Uncaught SyntaxError: Unexpected token ).

Вот два способа исправить это, которые включают добавление дополнительных скобок: (function foo(){ })() и (function foo(){ }()). Выражения, начинающиеся с function, считаются объявлениями функций; заключив эту функцию в (), она становится функциональным выражением, которое затем может быть выполнено с последующими (). Эти функции не видны в глобальной области видимости, и вы даже можете опустить ее имя, если вам не нужно ссылаться на нее внутри тела.

Вы также можете использовать оператор void: void function foo(){ }();. К сожалению, у такого подхода есть одна проблема. Результат вычисления заданного выражения всегда undefined, поэтому если ваша функция IIFE что-либо возвращает, вы не сможете это использовать. Пример:

const foo = void (function bar() {   return 'foo'; })(); console.log(foo); // undefined

В чем разница между переменной, которая является: `null`, `undefined` или необъявленной? Как бы вы проверили любое из этих состояний?

Необъявленные переменные создаются, когда вы присваиваете значение идентификатору, который ранее не был создан с помощью var, let или const. Необъявленные переменные будут определены глобально, за пределами текущей области видимости. В строгом режиме будет выброшена ReferenceError, если вы попытаетесь присвоить значение необъявленной переменной. Необъявленные переменные плохи так же, как плохи глобальные переменные. Избегайте их любой ценой! Чтобы проверить их, оберните их использование в блок try/catch.

function foo() {   x = 1; // Выбрасывает ReferenceError в строгом режиме } foo(); console.log(x); // 1

Переменная, которая является undefined, — это переменная, которая была объявлена, но ей не было присвоено значение. Она имеет тип undefined. Если функция не возвращает никакого значения в результате выполнения, и это значение присваивается переменной, то переменная также имеет значение undefined. Чтобы проверить это, используйте оператор строгого равенства (===) или typeof, который вернет строку 'undefined'. Обратите внимание, что вы не должны использовать оператор абстрактного равенства для проверки, так как он также вернет true, если значение равно null.

var foo; console.log(foo); // undefined console.log(foo === undefined); // true console.log(typeof foo === 'undefined'); // true console.log(foo == null); // true. Неправильно, не используйте это для проверки! function bar() {} var baz = bar(); console.log(baz); // undefined

Переменная, которая является null, будет явно присвоена значению null. Она представляет отсутствие значения и отличается от undefined в том смысле, что ей было явно присвоено значение. Чтобы проверить на null, просто сравните с помощью оператора строгого равенства. Обратите внимание, что, как и выше, вы не должны использовать оператор абстрактного равенства (==) для проверки, так как он также вернет true, если значение undefined.

var foo = null; console.log(foo === null); // true console.log(typeof foo === 'object'); // true console.log(foo == undefined); // true. Неправильно, не используйте это для проверки!

По личной привычке я никогда не оставляю свои переменные необъявленными или неприсвоенными. Я явно присваиваю им null после объявления, если я еще не собираюсь их использовать. Если вы используете линтер в своем рабочем процессе, он обычно также сможет проверить, что вы не ссылаетесь на необъявленные переменные.

Что такое замыкание и как/почему вы бы его использовали?

Замыкание — это комбинация функции и лексической среды, в которой эта функция была объявлена. Слово «лексическое» относится к тому факту, что лексическая область видимости использует место, где переменная объявлена в исходном коде, для определения того, где эта переменная доступна. Замыкания — это функции, которые имеют доступ к переменным внешней (объемлющей) функции — цепочке областей видимости даже после того, как внешняя функция вернула значение.

Почему вы бы его использовали?

  • Конфиденциальность данных / эмуляция приватных методов с помощью замыканий. Обычно используется в [модульном шаблоне].
  • [Частичные применения или каррирование].

Можете ли вы описать основное различие между циклом `.forEach` и циклом `.map()` и почему вы выбрали бы один из них?

Чтобы понять различия между двумя функциями, давайте рассмотрим, что каждая из них делает.

forEach

  • Итерирует по элементам в массиве.
  • Выполняет колбэк для каждого элемента.
  • Не возвращает значения.
const a = [1, 2, 3]; const doubled = a.forEach((num, index) => {   // Сделать что-нибудь с num и/или index. }); // doubled = undefined

map

  • Итерирует по элементам в массиве.
  • «Отображает» каждый элемент в новый элемент, вызывая функцию для каждого элемента, создавая в результате новый массив.
const a = [1, 2, 3]; const doubled = a.map((num) => {   return num * 2; }); // doubled = [2, 4, 6]

Основное отличие между .forEach и .map() состоит в том, что .map() возвращает новый массив. Если вам нужен результат, но вы не хотите изменять исходный массив, .map() — очевидный выбор. Если вам просто нужно проитерировать по массиву, forEach — хороший выбор.

Какой типичный вариант использования анонимных функций?

Их можно использовать в IIFE для инкапсуляции некоторого кода в локальной области видимости, чтобы объявленные в нем переменные не просачивались в глобальную область видимости.

(function () {   // Какой-то код здесь. })();

В качестве колбэка, который используется один раз и не нуждается в использовании где-либо еще. Код будет выглядеть более самодостаточным и читаемым, когда обработчики определены прямо внутри вызывающего их кода, вместо того чтобы искать тело функции в другом месте.

setTimeout(function () {   console.log('Hello world!'); }, 1000);

Аргументы для конструкций функционального программирования или Lodash (аналогично колбэкам).

const arr = [1, 2, 3]; const double = arr.map(function (el) {   return el * 2; }); console.log(double); // [2, 4, 6]

Как вы организуете свой код? (модульный паттерн, классическое наследование?)

В прошлом я использовал Backbone для своих моделей, что поощряет более ООП-подход, создавая модели Backbone и прикрепляя к ним методы.

Модульный шаблон по-прежнему великолепен, но в наши дни я использую React/Redux, которые используют однонаправленный поток данных, основанный на архитектуре Flux. Я бы представлял модели своего приложения, используя простые объекты, и писал бы чистые вспомогательные функции для манипулирования этими объектами. Состояние манипулируется с помощью действий и редьюсеров, как и в любом другом приложении Redux.

Я по возможности избегаю использования классического наследования. Когда и если я его использую, я придерживаюсь [этих правил].

В чем разница между хост-объектами и нативными объектами?

Нативные объекты — это объекты, которые являются частью языка JavaScript, определенные спецификацией ECMAScript, такие как String, Math, RegExp, Object, Function и т. д.

Хост-объекты предоставляются средой выполнения (браузером или Node), такие как window, XMLHTTPRequest и т. д.

Разница между: `function Person(){}`, `var person = Person()`, и `var person = new Person()`?

Этот вопрос довольно расплывчат. Я предполагаю, что он касается конструкторов в JavaScript. Технически говоря, function Person(){} — это просто обычное объявление функции. Принято использовать PascalCase для функций, предназначенных для использования в качестве конструкторов.

var person = Person() вызывает Person как функцию, а не как конструктор. Такой вызов является распространенной ошибкой, если функция предназначена для использования в качестве конструктора. Обычно конструктор ничего не возвращает, поэтому вызов конструктора как обычной функции вернет undefined, и это значение будет присвоено переменной, предназначенной для экземпляра.

var person = new Person() создает экземпляр объекта Person с помощью оператора new, который наследуется от Person.prototype. Альтернативой может быть использование Object.create, например: Object.create(Person.prototype).

function Person(name) {   this.name = name; } var person = Person('John'); console.log(person); // undefined console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined var person = new Person('John'); console.log(person); // Person { name: "John" } console.log(person.name); // "john"

В чем разница между `.call` и `.apply`?

Оба метода, .call и .apply, используются для вызова функций, и первый параметр будет использоваться в качестве значения this внутри функции. Однако .call принимает аргументы, разделенные запятыми, в качестве следующих аргументов, в то время как .apply принимает массив аргументов в качестве следующего аргумента. Легкий способ запомнить это: C для call и comma-separated (разделенные запятыми), и A для apply и an array of arguments (массив аргументов).

function add(a, b) {   return a + b; } console.log(add.call(null, 1, 2)); // 3 console.log(add.apply(null, [1, 2])); // 3

Объясните `Function.prototype.bind`.

Дословно взято из [MDN]:

Метод bind() создает новую функцию, которая при вызове имеет ключевое слово this, установленное на предоставленное значение, при этом заданная последовательность аргументов предшествует любым аргументам, предоставленным при вызове новой функции.

По моему опыту, он наиболее полезен для привязки значения this в методах классов, которые вы хотите передать другим функциям. Это часто делается в компонентах React.

Когда бы вы использовали `document.write()`?

document.write() записывает строку текста в поток документа, открытый с помощью document.open(). Когда document.write() выполняется после загрузки страницы, он вызывает document.open, который очищает весь документ (<head> и <body> удаляются!) и заменяет содержимое заданным значением параметра. Поэтому обычно это считается опасным и подверженным неправильному использованию.

В Интернете есть ответы, объясняющие, что document.write() используется в коде аналитики или [когда вы хотите включить стили, которые должны работать только при включенном JavaScript]. Его даже используют в HTML5 boilerplate для [параллельной загрузки скриптов и сохранения порядка выполнения]! Однако я подозреваю, что эти причины могут быть устаревшими, и в современное время их можно достичь без использования document.write(). Пожалуйста, поправьте меня, если я ошибаюсь в этом.

В чем разница между определением функций, определением функций по выводу и использованием строки UA?

Определение функций

Определение функций включает в себя выяснение того, поддерживает ли браузер определенный блок кода, и запуск другого кода в зависимости от того, поддерживает ли он его (или нет), чтобы браузер всегда мог обеспечить работоспособный опыт, а не сбоить/выдавать ошибки в некоторых браузерах. Например:

if ('geolocation' in navigator) {   // Можно использовать navigator.geolocation } else {   // Обработка отсутствия функции }

[Modernizr] — отличная библиотека для определения функций.

Определение функций по выводу

Определение функций по выводу проверяет наличие функции так же, как и определение функций, но использует другую функцию, потому что предполагает, что она также будет существовать, например:

if (document.getElementsByTagName) {   element = document.getElementById(id); }

Это не рекомендуется. Определение функций более надежно.

Строка UA

Это строка, сообщаемая браузером, которая позволяет сетевым протоколам идентифицировать тип приложения, операционную систему, поставщика программного обеспечения или версию программного обеспечения запрашивающего пользовательского агента. Она может быть доступна через navigator.userAgent. Однако строка сложна для анализа и может быть подделана. Например, Chrome сообщает себя как Chrome и Safari. Поэтому для обнаружения Safari необходимо проверить наличие строки Safari и отсутствие строки Chrome. Избегайте этого метода.

Объясните Ajax как можно подробнее.

Ajax (асинхронный JavaScript и XML) — это набор методов веб-разработки, использующих множество веб-технологий на стороне клиента для создания асинхронных веб-приложений. С помощью Ajax веб-приложения могут отправлять и получать данные с сервера асинхронно (в фоновом режиме), не мешая отображению и поведению существующей страницы. Отделяя уровень обмена данными от уровня представления, Ajax позволяет веб-страницам и, соответственно, веб-приложениям динамически изменять содержимое без необходимости перезагрузки всей страницы. На практике современные реализации обычно используют JSON вместо XML из-за преимуществ JSON, являющегося родным для JavaScript.

Для асинхронной связи часто используется API XMLHttpRequest или, в настоящее время, API fetch().

Каковы преимущества и недостатки использования Ajax?

Преимущества

  • Лучшая интерактивность. Новое содержимое с сервера может динамически изменяться без необходимости перезагрузки всей страницы.
  • Уменьшение количества соединений с сервером, поскольку скрипты и таблицы стилей нужно запрашивать только один раз.
  • Состояние может сохраняться на странице. Переменные JavaScript и состояние DOM будут сохраняться, потому что основная страница-контейнер не перезагружалась.
  • В основном, большинство преимуществ SPA.

Недостатки

  • Динамические веб-страницы сложнее добавлять в закладки.
  • Не работает, если JavaScript отключен в браузере.
  • Некоторые веб-сканеры не выполняют JavaScript и не видят содержимое, загруженное JavaScript.
  • Веб-страницам, использующим Ajax для получения данных, вероятно, придется объединять полученные удаленные данные с клиентскими шаблонами для обновления DOM. Для этого JavaScript должен быть проанализирован и выполнен в браузере, и низкопроизводительные мобильные устройства могут столкнуться с трудностями.
  • В основном, большинство недостатков SPA.

Объясните, как работает JSONP (и почему это не совсем Ajax).

JSONP (JSON с дополнением) — это метод, обычно используемый для обхода кросс-доменных политик в веб-браузерах, потому что запросы Ajax с текущей страницы к домену другого происхождения не разрешены.

JSONP работает путем выполнения запроса к кросс-доменному домену через тег <script> и обычно с параметром запроса callback, например: https://example.com?callback=printData. Затем сервер обертывает данные в функцию с именем printData и возвращает их клиенту.

<script>   function printData(data) {     console.log(`My name is ${data.name}!`);   } </script> <script src="[https://example.com?callback=printData](https://example.com?callback=printData)"></script>
printData({ name: 'Yang Shun' });

Клиент должен иметь функцию printData в своей глобальной области видимости, и функция будет выполнена клиентом, когда будет получен ответ от кросс-доменного домена.

JSONP может быть небезопасным и имеет некоторые последствия для безопасности. Поскольку JSONP — это действительно JavaScript, он может делать все, что может делать JavaScript, поэтому вы должны доверять поставщику данных JSONP.

В настоящее время [CORS] является рекомендуемым подходом, а JSONP рассматривается как хак.

Вы когда-нибудь использовали шаблонизацию JavaScript? Если да, то какие библиотеки вы использовали?

Да. Handlebars, Underscore, Lodash, AngularJS и JSX. Мне не нравилась шаблонизация в AngularJS, потому что она активно использовала строки в директивах, и опечатки оставались незамеченными. JSX — мой новый фаворит, поскольку он ближе к JavaScript, и там почти нет синтаксиса, который нужно изучать. В наши дни вы даже можете использовать литералы шаблонных строк ES2015 как быстрый способ создания шаблонов без использования стороннего кода.

const template = `<div>My name is: ${name}</div>`;

Однако имейте в виду потенциальную XSS при вышеуказанном подходе, так как содержимое не экранируется для вас, в отличие от библиотек шаблонизации.

Объясните "поднятие" (hoisting).

Хойстинг — это термин, используемый для объяснения поведения объявлений переменных в вашем коде. Переменные, объявленные или инициализированные с помощью ключевого слова var, будут иметь свои объявления «перемещены» вверх в область видимости своего модуля/функции, что мы называем хойстингом. Однако поднимается только объявление, присвоение (если оно есть) остается на месте.

Обратите внимание, что объявление на самом деле не перемещается — движок JavaScript анализирует объявления во время компиляции и узнает об объявлениях и их областях видимости. Просто легче понять это поведение, визуализируя объявления как поднятые в верхнюю часть их области видимости. Давайте объясним на нескольких примерах.

console.log(foo); // undefined var foo = 1; console.log(foo); // 1

Объявления функций поднимают тело, в то время как выражения функций (записанные в виде объявлений переменных) поднимают только объявление переменной.

// Объявление функции console.log(foo); // [Function: foo] foo(); // 'FOOOOO' function foo() {   console.log('FOOOOO'); } console.log(foo); // [Function: foo] // Выражение функции console.log(bar); // undefined bar(); // Uncaught TypeError: bar is not a function var bar = function () {   console.log('BARRRR'); }; console.log(bar); // [Function: bar]

Переменные, объявленные через let и const, также поднимаются. Однако, в отличие от var и function, они не инициализируются, и доступ к ним до объявления приведет к исключению ReferenceError. Переменная находится в «временной мертвой зоне» от начала блока до обработки объявления.

x; // undefined y; // Reference error: y is not defined var x = 'local'; let y = 'local';

Опишите всплытие событий.

Когда событие срабатывает на элементе DOM, оно пытается обработать событие, если к нему прикреплен слушатель, затем событие всплывает к его родителю, и происходит то же самое. Это всплытие происходит по всем предкам элемента вплоть до document. Всплытие событий — это механизм, лежащий в основе делегирования событий.

В чем разница между «атрибутом» и «свойством»?

Атрибуты определяются в HTML-разметке, но свойства определяются в DOM. Чтобы проиллюстрировать разницу, представьте, что у нас есть это текстовое поле в нашем HTML: <input type="text" value="Hello">.

const input = document.querySelector('input'); console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello

Но после того, как вы измените значение текстового поля, добавив к нему «World!», это станет:

console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello World!

Почему расширение встроенных объектов JavaScript — плохая идея?

Расширение встроенного/нативного объекта JavaScript означает добавление свойств/функций в его prototype. Хотя это может показаться хорошей идеей на первый взгляд, на практике это опасно. Представьте, что ваш код использует несколько библиотек, которые обе расширяют Array.prototype, добавляя один и тот же метод contains, реализации будут перезаписывать друг друга, и ваш код сломается, если поведение этих двух методов не совпадает.

Единственный случай, когда вы можете захотеть расширить нативный объект, — это когда вы хотите создать полифил, по сути, предоставляя свою собственную реализацию для метода, который является частью спецификации JavaScript, но может отсутствовать в браузере пользователя из-за того, что это более старый браузер.

Разница между событием `load` документа и событием `DOMContentLoaded` документа?

Событие DOMContentLoaded срабатывает, когда начальный HTML-документ полностью загружен и разобран, без ожидания загрузки таблиц стилей, изображений и подфреймов.

Событие load объекта window срабатывает только после того, как загружены DOM и все зависимые ресурсы и активы.

В чем разница между `==` и `===`?

== — это оператор абстрактного равенства, а === — оператор строгого равенства. Оператор == будет сравнивать на равенство после выполнения любых необходимых преобразований типов. Оператор === не будет выполнять преобразование типов, поэтому, если два значения не имеют одинаковый тип, === просто вернет false. При использовании == могут происходить странные вещи, например:

1 == '1'; // true 1 == [1]; // true 1 == true; // true 0 == ''; // true 0 == '0'; // true 0 == false; // true

Мой совет — никогда не использовать оператор ==, за исключением случаев удобства при сравнении с null или undefined, где a == null вернет true, если a является null или undefined.

var a = null; console.log(a == null); // true console.log(a == undefined); // true

Объясните политику одного источника в отношении JavaScript.

Политика одного источника предотвращает выполнение запросов JavaScript через границы доменов. Источник определяется как комбинация схемы URI, имени хоста и номера порта. Эта политика предотвращает доступ вредоносного скрипта на одной странице к конфиденциальным данным на другой веб-странице через объектную модель документа этой страницы.

Сделайте это работающим:

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
function duplicate(arr) {   return arr.concat(arr); } duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Или с ES6:

const duplicate = (arr) => [...arr, ...arr]; duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Почему это называется тернарным выражением, что означает слово "Ternary"?

«Тернарный» означает «из трех», и тернарное выражение принимает три операнда: условие проверки, выражение «тогда» и выражение «иначе». Тернарные выражения не являются специфичными для JavaScript, и я не уверен, почему они вообще есть в этом списке.

Что такое `"use strict";`? Каковы преимущества и недостатки его использования?

'use strict' — это оператор, используемый для включения строгого режима для целых скриптов или отдельных функций. Строгий режим — это способ выбрать ограниченный вариант JavaScript.

Преимущества:

  • Делает невозможным случайное создание глобальных переменных.
  • Заставляет выбросить исключение при присваиваниях, которые в противном случае молча бы провалились.
  • Заставляет выбросить исключение при попытках удалить неудаляемые свойства (где раньше попытка просто не имела бы никакого эффекта).
  • Требует уникальности имен параметров функции.
  • this является undefined в глобальном контексте.
  • Он ловит некоторые распространенные ошибки кодирования, выбрасывая исключения.
  • Он отключает функции, которые запутывают или плохо продуманы.

Недостатки:

  • Отсутствие многих функций, к которым некоторые разработчики могли привыкнуть.
  • Нет больше доступа к function.caller и function.arguments.
  • Конкатенация скриптов, написанных в разных строгих режимах, может вызвать проблемы.

В целом, я думаю, что преимущества перевешивают недостатки, и мне никогда не приходилось полагаться на функции, которые блокирует строгий режим. Я бы рекомендовал использовать строгий режим.

Создайте цикл for, который итерирует до `100`, выводя **"fizz"** для кратных `3`, **"buzz"** для кратных `5` и **"fizzbuzz"** для кратных `3` и `5`.

Ознакомьтесь с этой версией FizzBuzz от [Пола Айриша].

for (let i = 1; i <= 100; i++) {   let f = i % 3 == 0,     b = i % 5 == 0;   console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i); }

Однако я бы не советовал вам писать вышеуказанное на собеседованиях. Просто придерживайтесь длинного, но ясного подхода. Для более причудливых версий FizzBuzz ознакомьтесь с приведенной ниже ссылкой.

Почему, в общем, хорошей идеей является оставлять глобальную область видимости веб-сайта как есть и никогда ее не трогать?

Каждый скрипт имеет доступ к глобальной области видимости, и если все используют глобальное пространство имен для определения своих переменных, то, скорее всего, произойдут коллизии. Используйте модульный шаблон (IIFE) для инкапсуляции ваших переменных в локальном пространстве имен.

Почему вы использовали бы что-то вроде события `load`? Есть ли у этого события недостатки? Знаете ли вы какие-либо альтернативы и почему вы использовали бы их?

Событие load срабатывает в конце процесса загрузки документа. В этот момент все объекты в документе находятся в DOM, и все изображения, скрипты, ссылки и подфреймы завершили загрузку.

Событие DOM DOMContentLoaded сработает после того, как DOM для страницы будет построен, но не будет ждать завершения загрузки других ресурсов. Это предпочтительнее в некоторых случаях, когда вам не нужно, чтобы вся страница была загружена до инициализации.

Объясните, что такое одностраничное приложение и как сделать его SEO-дружелюбным.

Ниже приведено из замечательного [Руководства по фронтенду Grab], которое, по совпадению, написано мной!

В настоящее время веб-разработчики называют создаваемые ими продукты веб-приложениями, а не веб-сайтами. Хотя строгой разницы между этими двумя терминами нет, веб-приложения, как правило, очень интерактивны и динамичны, позволяя пользователю выполнять действия и получать ответ на свое действие. Традиционно браузер получает HTML от сервера и отображает его. Когда пользователь переходит на другой URL-адрес, требуется полная перезагрузка страницы, и сервер отправляет совершенно новый HTML на новую страницу. Это называется рендерингом на стороне сервера.

Однако в современных SPA вместо этого используется рендеринг на стороне клиента. Браузер загружает начальную страницу с сервера, а также сценарии (фреймворки, библиотеки, код приложения) и таблицы стилей, необходимые для всего приложения. Когда пользователь переходит на другие страницы, перезагрузка страницы не запускается. URL-адрес страницы обновляется с помощью [HTML5 History API]. Новые данные, необходимые для новой страницы, обычно в формате JSON, извлекаются браузером с помощью запросов [AJAX] к серверу. Затем SPA динамически обновляет страницу данными с помощью JavaScript, который уже был загружен при первоначальной загрузке страницы. Эта модель аналогична тому, как работают собственные мобильные приложения.

Преимущества:

  • Приложение кажется более отзывчивым, и пользователи не видят мерцания при навигации по страницам из-за полной перезагрузки страниц.
  • Меньше HTTP-запросов к серверу, так как одни и те же ресурсы не нужно загружать снова для каждой загрузки страницы.
  • Четкое разделение задач между клиентом и сервером; вы можете легко создавать новые клиенты для разных платформ (например, мобильные устройства, чат-боты, смарт-часы) без необходимости изменять код сервера. Вы также можете независимо изменять технологический стек на клиенте и сервере, если не нарушен контракт API.

Недостатки:

  • Более тяжелая первоначальная загрузка страницы из-за загрузки фреймворка, кода приложения и ресурсов, необходимых для нескольких страниц.
  • Существует дополнительный шаг, который необходимо выполнить на вашем сервере, который заключается в настройке его для маршрутизации всех запросов к одной точке входа и разрешения маршрутизации на стороне клиента.
  • SPA зависят от JavaScript для рендеринга контента, но не все поисковые системы выполняют JavaScript во время сканирования, и они могут видеть пустой контент на вашей странице. Это непреднамеренно наносит ущерб поисковой оптимизации (SEO) вашего приложения. Однако в большинстве случаев, когда вы создаете приложения, SEO не является самым важным фактором, поскольку не весь контент должен быть индексируемым поисковыми системами. Чтобы преодолеть это, вы можете либо рендерить свое приложение на стороне сервера, либо использовать такие службы, как [Prerender], для «рендеринга вашего JavaScript в браузере, сохранения статического HTML и возврата его краулерам».

Каков ваш опыт работы с Промисами и/или их полифиллами?

Обладаю рабочими знаниями в этой области. Промис — это объект, который может произвести одно значение когда-нибудь в будущем: либо разрешенное значение, либо причину, по которой оно не разрешено (например, произошла сетевая ошибка). Промис может находиться в одном из 3 возможных состояний: выполнен, отклонен или ожидает. Пользователи промисов могут прикреплять колбэки для обработки выполненного значения или причины отклонения.

Некоторые распространенные полифиллы — это $.deferred, Q и Bluebird, но не все из них соответствуют спецификации. ES2015 поддерживает Промисы из коробки, и полифиллы обычно не нужны в наши дни.

Каковы плюсы и минусы использования Промисов вместо колбэков?

Плюсы

  • Избегайте «ада колбэков», который может быть нечитаемым.
  • Упрощает написание последовательного асинхронного кода, который читабелен с .then().
  • Упрощает написание параллельного асинхронного кода с Promise.all().
  • С промисами не произойдут следующие сценарии, которые присутствуют только при использовании колбэков:   - Вызов колбэка слишком рано   - Вызов колбэка слишком поздно (или никогда)   - Вызов колбэка слишком мало или слишком много раз   - Непередача необходимых среды/параметров   - Проглатывание любых ошибок/исключений, которые могут произойти

Минусы

  • Немного более сложный код (спорно).
  • В старых браузерах, где ES2015 не поддерживается, вам необходимо загрузить полифилл, чтобы использовать его.

Каковы преимущества/недостатки написания кода JavaScript на языке, который компилируется в JavaScript?

Некоторые примеры языков, которые компилируются в JavaScript, включают CoffeeScript, Elm, ClojureScript, PureScript и TypeScript.

Преимущества:

  • Исправляет некоторые давние проблемы в JavaScript и препятствует использованию антишаблонов JavaScript.
  • Позволяет писать более короткий код, предоставляя некоторый синтаксический сахар поверх JavaScript, чего, я думаю, не хватает ES5, но ES2015 великолепен.
  • Статические типы великолепны (в случае TypeScript) для больших проектов, которые необходимо поддерживать с течением времени.

Недостатки:

  • Требуют процесса сборки/компиляции, так как браузеры запускают только JavaScript, и ваш код необходимо будет скомпилировать в JavaScript, прежде чем он будет передан браузерам.
  • Отладка может быть проблемой, если ваши исходные карты не соответствуют исходному коду до компиляции.
  • Большинство разработчиков не знакомы с этими языками и им придется их изучать. Есть затраты на обучение для вашей команды, если вы используете их для своих проектов.
  • Меньшее сообщество (зависит от языка), что означает, что ресурсы, учебные пособия, библиотеки и инструменты будет труднее найти.
  • Может отсутствовать поддержка IDE/редактора.
  • Эти языки всегда будут отставать от последнего стандарта JavaScript.
  • Разработчики должны знать, во что компилируется их код — потому что именно это будет фактически выполняться, и это имеет значение в конечном итоге.

Практически ES2015 значительно улучшил JavaScript и сделал его намного приятнее для написания. В наши дни я не вижу необходимости в CoffeeScript.

Какие инструменты и методы вы используете для отладки кода JavaScript?

  • React и Redux   - [React Devtools]   - [Redux Devtools]
  • Vue   - [Vue Devtools]
  • JavaScript   - [Chrome Devtools]   - Оператор debugger   - Старый добрый отладка через console.log

Какие языковые конструкции вы используете для итерации по свойствам объекта и элементам массива?

Для объектов:

  • Циклы for-in - for (var property in obj) { console.log(property); }. Однако это также будет итерировать по его унаследованным свойствам, и вам придется добавить проверку obj.hasOwnProperty(property) перед его использованием.
  • Object.keys() - Object.keys(obj).forEach(function (property) { ... }). Object.keys() — это статический метод, который перечисляет все перечисляемые свойства объекта, который вы ему передаете.
  • Object.getOwnPropertyNames() - Object.getOwnPropertyNames(obj).forEach(function (property) { ... }). Object.getOwnPropertyNames() — это статический метод, который перечисляет все перечисляемые и неперечисляемые свойства объекта, который вы ему передаете.

Для массивов:

  • Циклы for - for (var i = 0; i < arr.length; i++). Распространенная здесь ловушка заключается в том, что var находится в области видимости функции, а не в области видимости блока, и в большинстве случаев вам нужна переменная-итератор с областью видимости блока. ES2015 вводит let, который имеет область видимости блока, и вместо этого рекомендуется использовать его. Таким образом, это становится: for (let i = 0; i < arr.length; i++).
  • forEach - arr.forEach(function (el, index) { ... }). Эта конструкция иногда может быть более удобной, потому что вам не нужно использовать index, если вам нужны только элементы массива. Существуют также методы every и some, которые позволят вам завершить итерацию раньше.
  • Циклы for-of - for (let elem of arr) { ... }. ES6 вводит новый цикл, цикл for-of, который позволяет вам перебирать объекты, соответствующие итерируемому протоколу, такие как String, Array, Map, Set и т. д. Он сочетает в себе преимущества цикла for и метода forEach(). Преимущество цикла for состоит в том, что вы можете прервать его, а преимущество forEach() заключается в том, что он более лаконичен, чем цикл for, потому что вам не нужна переменная-счетчик. С циклом for-of вы получаете как возможность прервать цикл, так и более лаконичный синтаксис.

В большинстве случаев я бы предпочел метод .forEach, но это действительно зависит от того, что вы пытаетесь сделать. До ES6 мы использовали циклы for, когда нам нужно было преждевременно завершить цикл с помощью break. Но теперь с ES6 мы можем это делать с циклами for-of. Я бы использовал циклы for, когда мне нужна еще большая гибкость, например, инкрементирование итератора более одного раза за цикл.

Также, при использовании цикла for-of, если вам нужно получить доступ как к индексу, так и к значению каждого элемента массива, вы можете сделать это с помощью метода entries() массива ES6 и деструктуризации:

const arr = ['a', 'b', 'c']; for (let [index, elem] of arr.entries()) {   console.log(index, ': ', elem); }

Объясните разницу между изменяемыми и неизменяемыми объектами.

Неизменяемость является основным принципом функционального программирования и многое может предложить также объектно-ориентированным программам. Изменяемый объект — это объект, состояние которого может быть изменено после его создания. Неизменяемый объект — это объект, состояние которого не может быть изменено после его создания.

Примеры неизменяемых объектов в JavaScript?

В JavaScript некоторые встроенные типы (числа, строки) являются неизменяемыми, но пользовательские объекты, как правило, изменяемы. Некоторые встроенные неизменяемые объекты JavaScript — это Math, Date.

Вот несколько способов добавить/смоделировать неизменяемость для обычных объектов JavaScript.

Константные свойства объекта

Комбинируя writable: false и configurable: false, вы можете, по сути, создать константу (которую нельзя изменить, переопределить или удалить) как свойство объекта, например:

let myObject = {}; Object.defineProperty(myObject, 'number', {   value: 42,   writable: false,   configurable: false, }); console.log(myObject.number); // 42 myObject.number = 43; console.log(myObject.number); // 42

Предотвращение расширений

Если вы хотите запретить добавление новых свойств к объекту, но при этом оставить остальные свойства объекта без изменений, вызовите Object.preventExtensions(...):

var myObject = {   a: 2, }; Object.preventExtensions(myObject); myObject.b = 3; myObject.b; // undefined

В нестрогом режиме создание b завершается без ошибок. В строгом режиме оно выбрасывает TypeError.

Запечатывание

Object.seal() создает «запечатанный» объект, что означает, что он берет существующий объект и, по сути, вызывает на нем Object.preventExtensions(), а также помечает все его существующие свойства как configurable: false.

Таким образом, вы не только не можете добавлять новые свойства, но также не можете перенастраивать или удалять существующие свойства (хотя вы все еще можете изменять их значения).

Замораживание

Object.freeze() создает замороженный объект, что означает, что он берет существующий объект и, по сути, вызывает на нем Object.seal(), а также помечает все свойства «аксессора данных» как writable:false, чтобы их значения не могли быть изменены.

Этот подход является наивысшим уровнем неизменяемости, которого вы можете достичь для самого объекта, поскольку он предотвращает любые изменения объекта или любого из его прямых свойств (хотя, как упоминалось выше, содержимое любых ссылающихся на другие объекты не затрагивается).

var immutable = Object.freeze({});

Замораживание объекта не позволяет добавлять новые свойства к объекту и предотвращает удаление или изменение существующих свойств. Object.freeze() сохраняет перечисляемость, конфигурируемость, записываемость и прототип объекта. Он возвращает переданный объект и не создает замороженную копию.

Каковы плюсы и минусы неизменяемости?

Плюсы

  • Более простое обнаружение изменений — равенство объектов может быть определено производительным и простым способом через ссылочное равенство. Это полезно для сравнения различий объектов в React и Redux.
  • Программы с неизменяемыми объектами проще для понимания, поскольку вам не нужно беспокоиться о том, как объект может развиваться с течением времени.
  • Защитные копии больше не нужны, когда неизменяемые объекты возвращаются из функций или передаются в них, поскольку нет возможности их изменения.
  • Простое совместное использование через ссылки — одна копия объекта так же хороша, как и другая, поэтому вы можете кэшировать объекты или повторно использовать один и тот же объект несколько раз.
  • Потокобезопасность — неизменяемые объекты могут безопасно использоваться между потоками в многопоточной среде, поскольку нет риска их изменения в других одновременно выполняющихся потоках.
  • При использовании таких библиотек, как ImmutableJS, объекты изменяются с использованием структурного совместного использования, и для хранения нескольких объектов со схожими структурами требуется меньше памяти.

Минусы

  • Наивные реализации неизменяемых структур данных и операций с ними могут привести к чрезвычайно низкой производительности, поскольку каждый раз создаются новые объекты. Рекомендуется использовать библиотеки для эффективных неизменяемых структур данных и операций, использующих структурное совместное использование.
  • Выделение (и освобождение) множества мелких объектов вместо изменения существующих может вызвать снижение производительности. Сложность аллокатора или сборщика мусора обычно зависит от количества объектов в куче.
  • Циклические структуры данных, такие как графы, трудно построить. Если у вас есть два объекта, которые не могут быть изменены после инициализации, как вы можете заставить их указывать друг на друга?

Как вы можете добиться неизменяемости в своем коде?

Альтернатива — использовать объявления const в сочетании с упомянутыми выше методами создания. Для «мутирования» объектов используйте оператор распространения, Object.assign, Array.concat() и т. д., чтобы создавать новые объекты вместо того, чтобы мутировать исходный объект.

Примеры:

// Пример массива const arr = [1, 2, 3]; const newArr = [...arr, 4]; // [1, 2, 3, 4] // Пример объекта const human = Object.freeze({ race: 'human' }); const john = { ...human, name: 'John' }; // {race: "human", name: "John"} const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}

Объясните разницу между синхронными и асинхронными функциями.

Синхронные функции блокирующие, тогда как асинхронные функции таковыми не являются. В синхронных функциях операторы завершаются до того, как будет выполнен следующий оператор. В этом случае программа выполняется строго в порядке следования операторов, и выполнение программы приостанавливается, если один из операторов занимает очень много времени.

Асинхронные функции обычно принимают колбэк в качестве параметра, и выполнение продолжается на следующей строке сразу после вызова асинхронной функции. Колбэк вызывается только тогда, когда асинхронная операция завершена и стек вызовов пуст. Тяжелые операции, такие как загрузка данных с веб-сервера или запрос к базе данных, должны выполняться асинхронно, чтобы основной поток мог продолжать выполнять другие операции вместо того, чтобы блокироваться до завершения этой длительной операции (в случае браузеров пользовательский интерфейс зависнет).

Что такое цикл событий? В чем разница между стеком вызовов и очередью задач?

Цикл событий — это однопоточный цикл, который отслеживает стек вызовов и проверяет, есть ли какая-либо работа в очереди задач. Если стек вызовов пуст и в очереди задач есть функции обратного вызова, функция извлекается из очереди и помещается в стек вызовов для выполнения.

Если вы еще не смотрели выступление Филипа Роберта о цикле событий, вам стоит это сделать. Это одно из самых просматриваемых видео о JavaScript.

Объясните различия в использовании `foo` между `function foo() {}` и `var foo = function() {}`

Первое — это объявление функции, а второе — функциональное выражение. Ключевое различие заключается в том, что у объявлений функций поднимается тело, а у функциональных выражений нет (они имеют такое же поведение поднятия, как и переменные). Для более подробного объяснения поднятия см. вопрос выше о поднятии. Если вы попытаетесь вызвать функциональное выражение до его определения, вы получите ошибку Uncaught TypeError: XXX is not a function.

Объявление функции

foo(); // 'FOOOOO' function foo() {   console.log('FOOOOO'); }

Функциональное выражение

foo(); // Uncaught TypeError: foo is not a function var foo = function () {   console.log('FOOOOO'); };

В чем различия между переменными, созданными с помощью `let`, `var` или `const`?

Переменные, объявленные с помощью ключевого слова var, ограничены областью видимости функции, в которой они созданы, или, если созданы вне какой-либо функции, глобальным объектом. let и const имеют блочную область видимости, что означает, что они доступны только в пределах ближайшего набора фигурных скобок (функция, блок if-else или цикл for).

function foo() {   // Все переменные доступны внутри функций.   var bar = 'bar';   let baz = 'baz';   const qux = 'qux';   console.log(bar); // bar   console.log(baz); // baz   console.log(qux); // qux } console.log(bar); // ReferenceError: bar is not defined console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined
if (true) {   var bar = 'bar';   let baz = 'baz';   const qux = 'qux'; } // Переменные, объявленные var, доступны в любом месте области видимости функции. console.log(bar); // bar // Переменные, определенные let и const, недоступны за пределами блока, в котором они были определены. console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined

var позволяет поднимать переменные, то есть на них можно ссылаться в коде до их объявления. let и const этого не допускают, вместо этого выбрасывая ошибку.

console.log(foo); // undefined var foo = 'foo'; console.log(baz); // ReferenceError: cannot access lexical declaration 'baz' before initialization let baz = 'baz'; console.log(bar); // ReferenceError: cannot access lexical declaration 'bar' before initialization const bar = 'bar';

Повторное объявление переменной с var не вызовет ошибку, но let и const вызовут.

var foo = 'foo'; var foo = 'bar'; console.log(foo); // "bar" let baz = 'baz'; let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared

let и const отличаются тем, что let позволяет переназначать значение переменной, а const нет.

// Это нормально. let foo = 'foo'; foo = 'bar'; // Это вызывает исключение. const baz = 'baz'; baz = 'qux';

В чем различия между классами ES6 и конструкторами функций ES5?

Давайте сначала рассмотрим пример каждого из них:

// Конструктор функции ES5 function Person(name) {   this.name = name; } // Класс ES6 class Person {   constructor(name) {     this.name = name;   } }

Для простых конструкторов они выглядят довольно похоже.

Основное различие в конструкторе проявляется при использовании наследования. Если мы хотим создать класс Student, который является подклассом Person, и добавить поле studentId, то помимо вышеизложенного нам необходимо сделать следующее.

// Конструктор функции ES5 function Student(name, studentId) {   // Вызываем конструктор суперкласса для инициализации членов, производных от суперкласса.   Person.call(this, name);   // Инициализируем собственные члены подкласса.   this.studentId = studentId; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; // Класс ES6 class Student extends Person {   constructor(name, studentId) {     super(name);     this.studentId = studentId;   } }

Использовать наследование в ES5 гораздо многословнее, а версия ES6 проще для понимания и запоминания.

Можете ли вы предложить вариант использования для нового синтаксиса стрелочной функции =>? Чем этот новый синтаксис отличается от других функций?

Одно очевидное преимущество стрелочных функций — это упрощение синтаксиса, необходимого для создания функций, без необходимости использования ключевого слова function. this внутри стрелочных функций также привязан к объемлющей области видимости, что отличается от обычных функций, где this определяется вызывающим его объектом. Лексически ограниченный this полезен при вызове колбэков, особенно в компонентах React.

В чем преимущество использования стрелочного синтаксиса для метода в конструкторе?

Основное преимущество использования стрелочной функции в качестве метода внутри конструктора состоит в том, что значение this устанавливается во время создания функции и не может быть изменено после этого. Таким образом, когда конструктор используется для создания нового объекта, this всегда будет ссылаться на этот объект. Например, допустим, у нас есть конструктор Person, который принимает имя в качестве аргумента, и у него есть два метода для console.log этого имени: один как обычная функция, а другой как стрелочная функция:

const Person = function (firstName) {   this.firstName = firstName;   this.sayName1 = function () {     console.log(this.firstName);   };   this.sayName2 = () => {     console.log(this.firstName);   }; }; const john = new Person('John'); const dave = new Person('Dave'); john.sayName1(); // John john.sayName2(); // John // Значение 'this' обычной функции может быть изменено, но стрелочной функции — нет. john.sayName1.call(dave); // Dave (потому что "this" теперь является объектом dave) john.sayName2.call(dave); // John john.sayName1.apply(dave); // Dave (потому что 'this' теперь является объектом dave) john.sayName2.apply(dave); // John john.sayName1.bind(dave)(); // Dave (потому что 'this' теперь является объектом dave) john.sayName2.bind(dave)(); // John var sayNameFromWindow1 = john.sayName1; sayNameFromWindow1(); // undefined (потому что 'this' теперь является объектом window) var sayNameFromWindow2 = john.sayName2; sayNameFromWindow2(); // John

Главный вывод здесь заключается в том, что this может быть изменено для обычной функции, но контекст всегда остается тем же для стрелочной функции. Таким образом, даже если вы передаете свою стрелочную функцию в разные части своего приложения, вам не нужно беспокоиться об изменении контекста.

Это может быть особенно полезно в компонентах классов React. Если вы определяете метод класса для чего-то вроде обработчика щелчка с использованием обычной функции, а затем передаете этот обработчик щелчка в дочерний компонент в качестве свойства, вам также потребуется привязать this в конструкторе родительского компонента. Если вы вместо этого используете стрелочную функцию, нет необходимости также привязывать «this», так как метод автоматически получит свое значение «this» из своей объемлющей лексической области видимости.

Что такое функция высшего порядка?

Функция высшего порядка — это любая функция, которая принимает одну или несколько функций в качестве аргументов, которые она использует для работы с некоторыми данными, и/или возвращает функцию в качестве результата. Функции высшего порядка предназначены для абстрагирования повторяющихся операций. Классическим примером является map, которая принимает массив и функцию в качестве аргументов. Затем map использует эту функцию для преобразования каждого элемента в массиве, возвращая новый массив с преобразованными данными. Другие популярные примеры в JavaScript — forEach, filter и reduce. Функция высшего порядка не обязательно должна манипулировать только массивами, так как существует множество вариантов использования для возврата функции из другой функции. Function.prototype.bind — один из таких примеров в JavaScript.

Map

Допустим, у нас есть массив имен, которые нам нужно преобразовать в верхний регистр.

const names = ['irish', 'daisy', 'anna'];

Императивный способ будет таким:

const transformNamesToUppercase = function (names) {   const results = [];   for (let i = 0; i < names.length; i++) {     results.push(names[i].toUpperCase());   }   return results; }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

Использование .map(transformerFn) делает код короче и более декларативным.

const transformNamesToUppercase = function (names) {   return names.map((name) => name.toUpperCase()); }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

Можете ли вы привести пример деструктуризации объекта или массива?

Деструктуризация — это выражение, доступное в ES6, которое позволяет кратко и удобно извлекать значения объектов или массивов и помещать их в отдельные переменные.

Деструктуризация массива

// Присваивание переменной. const foo = ['one', 'two', 'three']; const [one, two, three] = foo; console.log(one); // "one" console.log(two); // "two" console.log(three); // "three"
// Обмен переменных let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1

Деструктуризация объекта

// Присваивание переменной. const o = { p: 42, q: true }; const { p, q } = o; console.log(p); // 42 console.log(q); // true

Шаблонные литералы ES6 предлагают большую гибкость при генерации строк, можете ли вы привести пример?

Шаблонные литералы помогают упростить интерполяцию строк или включение переменных в строку. До ES2015 было принято делать что-то вроде этого:

var person = { name: 'Tyler', age: 28 }; console.log(   'Hi, my name is ' + person.name + ' and I am ' + person.age + ' years old!', ); // 'Hi, my name is Tyler and I am 28 years old!'

С шаблонными литералами вы теперь можете создать тот же вывод следующим образом:

const person = { name: 'Tyler', age: 28 }; console.log(`Hi, my name is ${person.name} and I am ${person.age} years old!`); // 'Hi, my name is Tyler and I am 28 years old!'

Обратите внимание, что вы используете обратные кавычки, а не обычные кавычки, чтобы указать, что вы используете шаблонный литерал, и что вы можете вставлять выражения внутри заполнителей ${}.

Второй полезный вариант использования — создание многострочных строк. До ES2015 вы могли создать многострочную строку следующим образом:

console.log('This is line one.\nThis is line two.'); // This is line one. // This is line two.

Или, если вы хотели разбить ее на несколько строк в своем коде, чтобы не прокручивать вправо в текстовом редакторе для чтения длинной строки, вы также могли бы написать ее так:

console.log('This is line one.\n' + 'This is line two.'); // This is line one. // This is line two.

Однако шаблонные литералы сохраняют любое добавленное вами форматирование. Например, чтобы создать тот же многострочный вывод, который мы создали выше, вы можете просто сделать:

console.log(`This is line one. This is line two.`); // This is line one. // This is line two.

Еще один вариант использования шаблонных литералов — в качестве замены библиотек шаблонизации для простых интерполяций переменных:

const person = { name: 'Tyler', age: 28 }; document.body.innerHTML = `   <div>     <p>Name: ${person.name}</p>     <p>Age: ${person.age}</p>   </div> `;

Обратите внимание, что ваш код может быть подвержен XSS при использовании .innerHTML. Санитизируйте ваши данные перед отображением, если они поступили от пользователя!

Можете ли вы привести пример каррированной функции и объяснить, почему этот синтаксис предлагает преимущество?

Каррирование — это шаблон, при котором функция с более чем одним параметром разбивается на несколько функций, которые при последовательном вызове будут накапливать все необходимые параметры по одному. Этот метод может быть полезен для упрощения чтения и композиции кода, написанного в функциональном стиле. Важно отметить, что для того, чтобы функция была каррированной, она должна начинаться как одна функция, а затем разбиваться на последовательность функций, каждая из которых принимает один параметр.

function curry(fn) {   if (fn.length === 0) {     return fn;   }   function _curried(depth, args) {     return function (newArgument) {       if (depth - 1 === 0) {         return fn(...args, newArgument);       }       return _curried(depth - 1, [...args, newArgument]);     };   }   return _curried(fn.length, []); } function add(a, b) {   return a + b; } var curriedAdd = curry(add); var addFive = curriedAdd(5); var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

Каковы преимущества использования оператора расширения (spread syntax) и чем он отличается от оператора остатка (rest syntax)?

Оператор расширения ES6 очень полезен при кодировании в функциональной парадигме, поскольку мы можем легко создавать копии массивов или объектов, не прибегая к Object.create, slice или библиотечной функции. Эта языковая особенность часто используется в проектах Redux и RxJS.

function putDookieInAnyArray(arr) {   return [...arr, 'dookie']; } const result = putDookieInAnyArray(['I', 'really', "don't", 'like']); // ["I", "really", "don't", "like", "dookie"] const person = {   name: 'Todd',   age: 29, }; const copyOfTodd = { ...person };

Синтаксис остаточных параметров ES6 предлагает сокращенный способ включения произвольного числа аргументов, передаваемых функции. Это похоже на инверсию синтаксиса расширения: он принимает данные и помещает их в массив, а не распаковывает массив данных, и он работает в аргументах функций, а также в деструктурирующих присваиваниях массивов и объектов.

function addFiveToABunchOfNumbers(...numbers) {   return numbers.map((x) => x + 5); } const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15] const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4] const { e, f, ...others } = {   e: 1,   f: 2,   g: 3,   h: 4, }; // e: 1, f: 2, others: { g: 3, h: 4 }

Как вы можете обмениваться кодом между файлами?

Это зависит от среды JavaScript.

На клиенте (среда браузера), пока переменные/функции объявлены в глобальной области видимости (window), все скрипты могут ссылаться на них. В качестве альтернативы, используйте Asynchronous Module Definition (AMD) через RequireJS для более модульного подхода.

На сервере (Node.js) распространенным способом было использование CommonJS. Каждый файл рассматривается как модуль, и он может экспортировать переменные и функции, прикрепляя их к объекту module.exports.

ES2015 определяет синтаксис модулей, который призван заменить как AMD, так и CommonJS. Он будет поддерживаться как в браузерных, так и в Node-средах.

Почему может потребоваться создание статических членов класса?

Статические члены класса (свойства/методы) не привязаны к конкретному экземпляру класса и имеют одно и то же значение независимо от того, какой экземпляр на них ссылается. Статические свойства обычно являются переменными конфигурации, а статические методы обычно представляют собой чистые служебные функции, которые не зависят от состояния экземпляра.