Собрали подборку самых частых вопросов на собеседовании фронтенд-разработчика с подробными ответами. Материал охватывает основы языка, асинхронность, работу с DOM, объекты, функции и ES6+.
Если вы готовитесь к собеседованию или хотите закрепить базу — этот чит-лист для вас. А чтобы знания не оставались теорией, попробуйте решить задачи на практике на Девстанции — интерактивной платформе с паутиной задач, системой уровней и AI-ревью кода.
ECMAScript — это спецификация, стандарт скриптовых языков программирования, он является основой JS, поэтому любые изменения ECMAScript отражаются на JS.
ECMAScript сам по себе — это не язык программирования. А вот JavaScript — это язык программирования, реализующий стандарт ECMAScript.
Область видимости — это область, ограничивающая доступ к переменным и функциям внутри себя, как бы инкапсулирующая их. JS имеет три типа областей видимости: глобальная, функциональная и блочная (ES6).
Глобальная — переменные и функции, объявленные в глобальном пространстве имен, имеют глобальную область видимости и доступны из любого места в коде.
Функциональная — переменные, функции и параметры, объявленные внутри функции, доступны только внутри этой функции.
Блочная — переменные, объявленные с помощью ключевых слов let и const, доступны только внутри блока {}, в котором были объявлены.
Область видимости — это также набор правил, по которым осуществляется поиск переменной. Если переменной не существует в текущей области видимости, её поиск производится выше, во внешней по отношению к текущей области видимости. Если и во внешней области видимости переменная отсутствует, её поиск продолжается вплоть до глобальной области видимости. Если в глобальной области видимости переменная обнаружена, поиск прекращается, если нет — выбрасывается исключение. Поиск осуществляется по ближайшим к текущей областям видимости и останавливается с нахождением переменной. Это называется цепочкой областей видимости (Scope Chain).
Поднятие (hoisting) — это термин, описывающий подъем переменной или функции в глобальную или функциональную область видимости.
Контекст выполнения имеет две фазы — компиляция и выполнение.
Компиляция. В этой фазе объявления функций (function declarations) и переменные, объявленные с помощью ключевого слова var, поднимаются в самый верх глобальной (или функциональной) области видимости — как бы перемещаются в начало нашего кода. Переменные var при этом получают значение undefined, а функции — своё тело. Это объясняет, почему мы можем вызывать функции до их объявления.
Выполнение. В этой фазе переменным присваиваются значения, а функции вызываются и выполняются.
Запомните:
function foo() {}) поднимаются целиком — и имя, и тело.var поднимаются, но с значением undefined до момента присваивания.let и const технически поднимаются, но остаются в так называемой зоне видимости времён (Temporal Dead Zone, TDZ) до момента объявления. Обращение к ним до объявления вызывает ошибку ReferenceError.const foo = () => {}) не поднимаются — только присваивание переменной.«use strict» — это директива ES5, которая заставляет весь наш код или код отдельной функции выполняться в строгом режиме. Строгий режим вводит некоторые ограничения по написанию кода, тем самым позволяя избегать ошибок на ранних этапах.
Ограничения строгого режима:
evalthis по умолчанию является undefinedКонвертация встроенных типов данных друг в друга в JavaScript называется приведением типов (coercion). Оно бывает явное (explicit) и неявное (implicit).
Пример явного приведения:
let a = "42"; // строка
let b = Number(a);
a; // "42" - строка
b; // 42 - числоПример неявного приведения:
let a = "42"; // строка
let b = a * 1; // строка "42" неявно приводится к числу
a; // "42" - строка
b; // 42 - числоОсобенно важно понимать, как происходит приведение к логическому типу, так как оно часто происходит в условиях.
Следующие значения приводятся к false:
"" (пустая строка)NaNnull, undefinedfalseА эти — к true:
«hello» (любая непустая строка, даже " " — строка с пробелом)true[], [1, "2"] (любой массив, даже пустой){}, {a: 42} (любой объект, даже пустой)function foo() {} (любая функция)Разница между оператором нестрогого равенства == и оператором строгого равенства === состоит в том, что первый сравнивает значения, осуществляя их приведение к одному типу (Coersion), а второй — без такого преобразования.
console.log(10 == "10"); // true
console.log(10 === "10"); // falseОператор строгого равенства === выдаст true только в том случае, если сравниваемые сущности относятся к одному и тому же типу данных и равны по значениям.
undefined представляет собой значение по умолчанию:
console.log(1)null — это значение, которое присваивается переменной явно.
При сравнении null и undefined мы получаем true, когда используем оператор ==, и false при использовании оператора ===.
typeof null == 'object' всегда будет возвращать true по историческим причинам.
От сообщества поступало предложение исправить эту ошибку, изменив typeof null = 'object' на typeof null = 'null', но оно было отклонено в интересах сохранения обратной совместимости (такое изменение повлекло бы за собой большое количество ошибок).
Для проверки, является ли значение null, можно использовать оператор строгого равенства ===:
function isNull(value) {
return value === null;
}this ссылается на значение объекта, который в данный момент выполняет функцию. Значение this определяется в момент вызова функции, а не объявления.
Правила определения this:
this указывает на объект:const user = {
name: "Alex",
greet() {
console.log(this.name); // 'Alex'
},
};
user.greet(); // this === userthis равен undefined (strict mode) или глобальному объекту (non-strict):function showThis() {
console.log(this);
}
showThis(); // undefined (strict mode) или windowthis указывает на создаваемый экземпляр:class User {
constructor(name) {
this.name = name;
}
}
const u = new User("Alex"); // this === uthis. Наследуют this из внешнего лексического окружения:const user = {
name: "Alex",
greet: () => {
console.log(this.name); // undefined — this берётся из внешней области
},
};call, apply, bind — явно задают this:function greet() {
console.log(this.name);
}
const user = { name: "Alex" };
greet.call(user); // 'Alex'
greet.apply(user); // 'Alex'
const bound = greet.bind(user);
bound(); // 'Alex'this указывает на элемент, на котором висит обработчик:button.addEventListener("click", function () {
console.log(this); // <button>...</button>
});
// В стрелочной функции this будет внешний объект, а не элементthis равен undefined (в модулях ES6 strict mode включён по умолчанию).function makeCounter(initial = 0) {
let counter = initial;
return function () {
return (counter += 1);
};
}
const counterA = makeCounter(0);
console.log(counterA()); // 1
console.log(counterA()); // 2
const counterB = makeCounter(100);
console.log(counterB()); // 101В примере кода выше мы определяем функцию makeCounter(), которая фактически является фабричной функцией. При вызове она возвращает дочернюю функцию, которая имеет доступ к лексическому окружению внешней функции, то есть функции makeCounter(). Таким образом, возвращаемая функция запоминает переменную counter и в последующем изменяет её значение.
Прототип — это независимый объект, хранящий свойства и методы, доступные всем объектам, чей __proto__ ссылается на него (на этот прототип). На этом принципе строится прототипное наследование в JS.
const o = {};
console.log(o.toString()); // [object Object]Несмотря на то, что объект o не имеет свойства toString, обращение к этому свойству не вызывает ошибки. Если определенного свойства нет в объекте, его поиск осуществляется сначала в прототипе объекта, затем в прототипе прототипа объекта и так до тех пор, пока свойство не будет найдено. Это называется цепочкой прототипов. На вершине цепочки прототипов находится объект, доступный по обращению Object.prototype.
console.log(o.toString === Object.prototype.toString); // trueconst o1 = {};
console.log(o1.toString); // [object Object]
const o2 = Object.create(null);
console.log(o2.toString); // o2.toString is not a functionhoistedFunc();
notHoistedFunc();
function hoistedFunc() {
console.log("I am hoisted");
}
var notHoistedFunc = function () {
console.log("I will not be hoisted!");
};Вызов notHoistedFunc приведет к ошибке, а вызов hoistedFunc нет, потому что hoistedFunc «всплывает», поднимается в глобальную область видимости, а notHoistedFunc нет.
(argument1, argument2, ...argumentN) => {
// тело функции
};Это анонимные функции с особым синтаксисом, выполняющиеся в контексте включающей их области видимости, то есть в контексте блока, в котором они объявлены.
У стрелочных функций нет доступа к объекту arguments. Также они не имеют своего this, а следовательно их нельзя использовать как конструкторы с ключевым словом new.
IIFE (Immediately Invoked Function Expression) — это функция, которая вызывается или выполняется сразу же после создания или объявления.
Для создания IIFE необходимо обернуть функцию в круглые скобки (оператор группировки), превратив ее в выражение, и затем вызвать ее с помощью ещё одних круглых скобок.
(function () {})()(function () {})()(function named(params) {})()(() => {})(
function (global) {},
)(window);
const utility = (function () {
return {
// утилиты
};
})();Все эти примеры являются валидными. Предпоследний пример показывает, что мы можем передавать параметры в IIFE. Последний пример показывает, что мы можем сохранить результат IIFE в переменной.
Лучшее использование IIFE — это выполнение функций настройки инициализации и предотвращение конфликтов имен с другими переменными в глобальной области видимости (загрязнение глобального пространства имен).
Коллбэк — это функция, которая передается в другую функцию как аргумент и выполняется после того, как закончатся какие-то другие операции.
function modifyArray(arr, callback) {
arr.push(100);
callback();
}
let arr = [1, 2, 3, 4, 5];
modifyArray(arr, function () {
console.log("массив модифицирован", arr);
});Ключевое слово new используется в функциях-конструкторах для создания нового объекта (нового экземпляра класса).
function Employee(name, position, yearHired) {
this.name = name;
this.position = position;
this.yearHired = yearHired;
}
const emp = new Employee("Marko Polo", "Software Development", 2017);Ключевое слово new делает 4 вещи:
this.functionName.prototype.this, если не указано иное.arguments — это коллекция аргументов, передаваемых функции. Он имеет свойство length и поддерживает обращение по индексу к элементам, но не является массивом, из-за чего не имеет методов forEach, reduce, filter, map и так далее.
Преобразовать arguments в массив можно, например, с помощью Array.prototype.slice.
В стрелочных функциях объект arguments не работает.
call используется для привязки определенного объекта к значению this вызываемой функции.
Отличие от apply состоит в том, что в call аргументы передаются через запятую.
apply используется для привязки определенного объекта к значению this вызываемой функции.
Отличие от call состоит в том, что в apply аргументы передаются в виде массива.
Метод bind() создает новую функцию, для которой зафиксирован this и последовательность аргументов, предшествующих аргументам при вызове новой функции.
Удобно использовать для привязки контекста или закрепления параметров.
function fullName() {
return "Hello, this is " + this.first + " " + this.last;
}
console.log(fullName()); // => Hello this is undefined undefined
var person = { first: "Foo", last: "Bar" };
console.log(fullName.bind(person)()); // => Hello this is Foo BarКлассы — это относительно новый способ написания функций-конструкторов в JS. Это синтаксический сахар для функций-конструкторов. В основе классов лежат те же прототипы и прототипное наследование:
// ES5
function Person(firstName, lastName, age, address) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
Person.self = function () {
return this;
};
Person.prototype.toString = function () {
return "[object Person]";
};
Person.prototype.getFullName = function () {
return this.firstName + " " + this.lastName;
};
// ES6
class Person {
constructor(firstName, lastName, age, address) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
static self() {
return this;
}
toString() {
return "[object Person]";
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}Наследование:
// ES5
function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
Person.call(this, firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.describe = function () {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
};
Employee.prototype.toString = function () {
return "[object Employee]";
};
// ES6
class Employee extends Person {
constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
describe() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
}
toString() {
return "[object Employee]";
}
}Модули позволяют объединять (использовать) код из разных файлов и избавляют нас от необходимости держать весь код в одном большом файле. До появления модулей в JS существовало две популярные системы модулей:
Синтаксис модулей простой: мы используем import для импорта функциональности или значений из другого файла или файлов и export для экспорта.
// Экспорт функциональности в другой файл (именной экспорт)
export function isNull(val) {
return val === null;
}
export function isUndefined(val) {
return val === undefined;
}
// Импорт функциональности
import {
isNull,
isUndefined,
isNullOrUndefined as isValid,
} from "./helpers.js";
// Экспорт по умолчанию
export default class Helpers {
static isNull(val) {
return val === null;
}
}
// Импорт по умолчанию
import Helpers from "./helpers.js";Деструктуризация в JavaScript позволяет извлечь данные из массива или свойства объекта и присвоить их отдельным переменным. Деструктуризация удобна тем, что позволяет не писать лишний код для доступа к данным внутри объектов/массивов по индексам или ключам.
Деструктуризация массива:
let array = [1, 2, 3];
let [x, y, z] = array;
console.log(x); // 1
console.log(y); // 2
console.log(z); // 3Деструктуризация объекта:
let user = {
name: "Василий",
lastname: "Пупкин",
age: 30,
};
let { name, lastname, age } = user;
console.log(name); // "Василий"
console.log(lastname); // "Пупкин"
console.log(age); // 30Spread оператор (оператор расширения) «берет» каждый отдельный элемент итерируемого объекта (массив) и «распаковывает» его в другой итерируемый объект (массив).
const tvSeriesOne = ["Ozark", "Fargo", "Dexter"];
const tvSeriesTwo = ["Mr. Robot", "Barry", "Suits"];
const tvSeries = [...tvSeriesOne, ...tvSeriesTwo];
console.log(tvSeries);
// [ "Ozark", "Fargo", "Dexter", "Mr. Robot", "Barry", "Suits" ]Оператор rest ... (три точки) похож на оператор spread, но выполняет противоположную функцию.
Spread забирает каждый элемент из массива и распаковывает в новый массив. Оператор rest забирает каждый элемент из массива и создает из них новый массив.
Есть 2 типа задач, где оператор rest используется чаще всего — в функциях и в процессе деструктуризации.
function buyPizza(price, ...rest) {
console.log(price); // 500
console.log(rest);
// Array(4) [ 3, 10, 6, 20 ]
}
buyPizza(500, 3, 10, 6, 20);const pizza = ["Pepperoni", 2222, 5, 6, 10, 30, 1];
const [name, id, ...rest] = pizza;
console.log(name); // Pepperoni
console.log(id); // 2222
console.log(rest);
// [ 5, 6, 10, 30, 1 ]Шаблонные литералы — относительно новый способ создания строк в JS. Шаблонные литералы создаются с помощью двойных обратных кавычек:
// ES5
var greet = "Hi I'm Mark";
// ES6
let greet = `Hi I'm Mark`;В шаблонных литералах нам не нужно экранировать одинарные кавычки.
// ES5
var lastWords = "\n" + " I \n" + " am \n" + "Iron Man \n";
// ES6
let lastWords = `
I
am
Iron Man
`;В ES6 нам не нужно использовать управляющую последовательность "\n" для перевода строки.
// ES5
function greet(name) {
return "Hello " + name + "!";
}
// ES6
function greet(name) {
return `Hello ${name}!`;
}В ES6 нам не нужно использовать конкатенацию строк для объединения текста с переменной: мы можем использовать выражение ${expr} для получения значения переменной.
Map — это коллекция ключ/значение наподобие Object, но с более удобным и декларативным API и некоторыми функциональными особенностями. Например, Map позволяет использовать ключи любого типа.
Методы и свойства:
new Map() — создаёт коллекциюMap.prototype.set(key, value) — записывает по ключу key значение valueMap.prototype.get(key) — возвращает значение по ключу или undefinedMap.prototype.has(key) — возвращает true, если ключ присутствует в коллекцииMap.prototype.delete(key) — удаляет элемент по ключуMap.prototype.clear() — очищает коллекциюMap.prototype.size — возвращает текущее количество элементовlet map = new Map();
map.set("1", "str1"); // строка в качестве ключа
map.set(1, "num1"); // цифра как ключ
map.set(true, "bool1"); // булево значение как ключ
// обычный объект Object приводит ключи к строкам?
// Map сохраняет тип ключей:
alert(map.get(1)); // "num1"
alert(map.get("1")); // "str1"
alert(map.size); // 3Для перебора коллекции Map есть 3 метода:
Map.prototype.keys() — возвращает итерируемый объект по ключамMap.prototype.values() — возвращает итерируемый объект по значениямMap.prototype.entries() — возвращает итерируемый объект по парам [ключ, значение], используется по умолчанию в for..ofВ отличие от обычных объектов, в Map перебор происходит в том же порядке, в каком происходило добавление элементов.
Set — это особый вид коллекции: «множество» значений (без ключей), где каждое значение может появляться только один раз.
Его основные методы:
new Set(iterable) — создаёт Set, копирует значения из итерируемого объектаSet.prototype.add(value) — добавляет значение (если оно уже есть, ничего не делает)Set.prototype.delete(value) — удаляет значениеSet.prototype.has(value) — возвращает true, если значение присутствуетSet.prototype.clear() — удаляет все значенияSet.prototype.size — возвращает количество элементовОсновная «изюминка» — при повторных вызовах set.add() с одним и тем же значением, оно добавляется в коллекцию лишь единожды.
Set имеет те же методы для перебора, что и Map: keys(), values(), entries().
WeakSet и WeakMap — это специальные структуры данных в JavaScript, которые отличаются особенностью хранения ссылок на объекты.
В обычных Set и Map хранятся сильные ссылки на объекты. Это значит, что пока существует ссылка на объект в этих структурах, сборщик мусора не удалит этот объект из памяти, даже если больше нигде в коде нет ссылок на него.
И напротив, в WeakSet и WeakMap хранятся слабые ссылки. Это означает, что если объект, на который есть ссылка в этих структурах, больше недоступен в коде (то есть нигде больше нет сильных ссылок на него), то сборщик мусора может удалить этот объект из памяти, даже если в WeakSet или WeakMap все еще есть ссылка на него. Таким образом, использование слабых ссылок позволяет не держать в памяти ненужные больше объекты и экономить память.
Кроме того, в WeakMap в качестве ключей могут использоваться только объекты, а не примитивные значения. А в WeakSet хранятся только объекты, без ключей.
const promise = new Promise(function (resolve, reject) {
// Эта функция будет вызвана автоматически
// В ней можно делать любые асинхронные операции,
// А когда они завершатся — нужно вызвать одно из:
// resolve(результат) при успешном выполнении
// reject(ошибка) при ошибке
});async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("готово!"), 1000);
});
let result = await promise; // будет ждать, пока промис не выполнится (*)
alert(result); // "готово!"
}
f();async/await — относительно новый способ написания асинхронного (неблокирующего) кода в JS. Он делает код более читаемым и чистым, чем промисы и функции обратного вызова.
Использование ключевого слова async перед функцией заставляет её возвращать промис.
Ключевое слово await можно использовать только внутри асинхронной функции. await ожидает завершения выражения справа, чтобы вернуть его значение перед выполнением следующей строчки кода.
async function fetchUser() {
try {
const response = await fetch("/api/user");
const data = await response.json();
return data;
} catch (error) {
console.error("Ошибка загрузки:", error.message);
}
}Это эквивалентно использованию .catch() на промисе, но записывается в более привычном синтаксисе:
Генераторы — специальный тип функций в JavaScript. Они отличаются от обычных тем, что могут приостанавливать своё выполнение, возвращать промежуточный результат и далее возобновлять его позже, в произвольный момент времени.
Для объявления генератора используется синтаксическая конструкция: function* (функция со звёздочкой). Её называют «функция-генератор» (generator function).
При запуске generateSequence() код такой функции не выполняется. Вместо этого она возвращает специальный объект, который как раз и называют «генератором». Можно воспринимать генератор как «замороженный вызов функции». При создании генератора код находится в начале своего выполнения.
Основным методом генератора является next(). При вызове он возобновляет выполнение кода до ближайшего ключевого слова yield. По достижении yield выполнение приостанавливается, а значение — возвращается во внешний код:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
alert(JSON.stringify(one)); // {value: 1, done: false}
let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, done: true}Новые вызовы generator.next() больше не имеют смысла. Впрочем, если они и будут, то не вызовут ошибки, но будут возвращать один и тот же объект: {done: true}.
DOM (Document Object Model) — это специальная древовидная структура, которая позволяет управлять HTML-разметкой из JavaScript-кода. Управление обычно состоит из добавления и удаления элементов, изменения их стилей и содержимого.
Браузер создаёт DOM при загрузке страницы, складывает его в переменную document и сообщает, что DOM создан, с помощью события DOMContentLoaded.
Поиск элементов:
// По ID
const el = document.getElementById("app");
// По CSS-селектору (первое совпадение)
const btn = document.querySelector(".button");
// По CSS-селектору (все совпадения)
const items = document.querySelectorAll(".item");Изменение содержимого:
el.textContent = "Новый текст"; // текстовый контент
el.innerHTML = "<b>Жирный</b>"; // HTML-содержимое
el.setAttribute("id", "new-id"); // атрибуты
el.classList.add("active"); // работа с классами
el.style.color = "red"; // инлайн-стилиСоздание и вставка элементов:
const div = document.createElement("div");
div.textContent = "Привет!";
document.body.appendChild(div); // в конец
parent.insertBefore(div, reference); // перед referenceУдаление элементов:
el.remove(); // современный способ
parent.removeChild(el); // старый способКогда какое-либо событие происходит в элементе DOM, оно на самом деле происходит не только в нем. Событие «распространяется» от объекта window до вызвавшего его элемента (event.target). При этом событие последовательно пронизывает всех предков целевого элемента.
Этот процесс называется Event Propagation (распространение события).
Event Propagation имеет три стадии:
window и опускается до цели события через всех ее предков.event.target, последовательно проходит через всех его предков и достигает объекта window.Всплытие — это фаза события, когда оно переходит от элемента-инициатора (event.target) к объекту window, проходя по пути через все элементы, лежащие между ними.
Погружение — это фаза, в которой событие опускается от объекта window до цели события через всех его предков.
Метод event.preventDefault() отключает поведение элемента по умолчанию. Если использовать этот метод в элементе form, то он предотвратит отправку формы (submit). Если использовать его в contextmenu, то контекстное меню будет отключено. Данный метод часто используется в keydown для переопределения клавиатуры, например, при создании музыкального/видео плеера или текстового редактора.
Метод event.stopPropagation() отключает распространение события (его всплытие или погружение).
Делегирование событий — подход при работе с событиями DOM-дерева, при котором обработчики событий добавляются не на каждый конкретный элемент, а только на общий родительский, в то время как необходимость вызова обработчика для конкретного интересующего нас элемента определяется через инициатора события, узнать который можно из свойства объекта события event.target.
Такой подход возможен благодаря особенностям событийной модели DOM-дерева, а конкретно такой особенности, как всплытие событий.
Shadow DOM (от англ. «shadow» — тень и «DOM» — Document Object Model) — это технология, которая позволяет создавать изолированные деревья DOM внутри основного дерева DOM веб-страницы.
Shadow DOM используется для инкапсуляции. Благодаря нему в компоненте есть собственное «теневое» DOM-дерево, к которому нельзя просто так обратиться из главного документа, у него могут быть изолированные CSS-правила и т.д.
Средствами теневого DOM устроены и стилизованы сложные браузерные элементы управления, например, <input type="range">. Браузер рисует их своими силами и по своему усмотрению. Их DOM-структура обычно нам не видна, но в инструментах разработчика можно её посмотреть. К примеру, в Chrome для этого нужно активировать пункт «Show user agent shadow DOM».
Мы не можем получить доступ к теневому DOM встроенных элементов с помощью обычных JavaScript-вызовов или с помощью селекторов. Это не просто обычные потомки, это мощное средство инкапсуляции.
Есть 3 основных метода хранения данных в браузере:
Используются для хранения пар ключ-значение. Данные, сохраненные в них, сохраняются после обновления страницы. При этом только LocalStorage может сохранять данные после перезапуска браузера. Оба хранилища могут использовать только строки в качестве ключей и значений, поэтому объекты необходимо преобразовать с помощью JSON.stringify().
Небольшие строки данных, которые хранятся в браузере. Cookie обычно устанавливаются веб-сервером с использованием заголовка Set-Cookie. Браузер затем автоматически добавляет их почти ко всем запросам на тот же домен с использованием заголовка Cookie. Один экземпляр cookie может содержать до 4 КБ данных. В зависимости от браузера, допускается более 20 cookie на сайт.
Встроенная база данных, более мощная, чем LocalStorage. Это NoSQL-хранилище данных в формате JSON внутри браузера, где доступны несколько типов ключей, а значения могут быть практически любым. IndexedDB поддерживает асинхронный доступ, транзакции для обеспечения согласованности данных и создание индексов для эффективного поиска. Позволяет хранить больше данных, чем LocalStorage, может быть связана с Service Workers и другими технологиями, которые обеспечивают функционирование PWA в оффлайне.
localStorage может хранить до 10 МБ данных, в то время как sessionStorage может хранить только до 5 МБ данных.localStorage данные не удаляются, когда закрывается браузер или вкладка. И напротив, данные в sessionStorage удаляются, когда закрывается вкладка или окно браузера.localStorage данные доступны в любом окне браузера, в то время как данные из sessionStorage доступны только из того же окна браузера, где они были сохранены.Если объект не содержит вложенных объектов, для клонирования можно использовать оператор расширения ... или метод Object.assign():
const obj = {
firstName: "Василий",
lastName: "Пупкин",
};
const copy1 = { ...obj };
console.log(copy1); // {firstName: 'Василий', lastName: 'Пупкин'}
// или
const copy2 = Object.assign({}, obj);
console.log(copy2); // {firstName: 'Василий', lastName: 'Пупкин'}Если объект содержит вложенные объекты, нужно выполнить глубокое копирование. Рекомендуемый способ — встроенный метод structuredClone(), доступный во всех современных браузерах:
const obj = {
data: {
id: 1,
},
};
const copy = structuredClone(obj);structuredClone поддерживает большинство встроенных типов данных (Date, RegExp, Map, Set, ArrayBuffer и др.), в отличие от JSON, который теряет undefined, функции, Symbols и т.д.
Другие варианты глубокого копирования — JSON.parse(JSON.stringify(obj)) (относительно медленный, с ограничениями) и метод cloneDeep из библиотеки lodash.
Символ (Symbol) — примитивный тип, значения которого создаются с помощью вызова функции Symbol. Каждый созданный символ уникален.
const sym = Symbol();
const symTwo = Symbol();
console.log(sym === symTwo);
// falseСимволы могут использоваться в качестве имён свойств в объектах. Символьные свойства могут быть прочитаны только при прямом обращении и не видны при обычных операциях.
Для создания символа нужно вызвать функцию Symbol. Символы используются для создания скрытых свойств объектов. В отличие от свойств, ключом которых является строка, символьные свойства может читать только владелец символа. Скрытые свойства не видны при его обходе с помощью for...in.
Это может пригодиться, когда необходимо добавить свойства объекту, который могут модифицировать другие части программы. Таким образом только вы сможете читать созданное свойство, а гарантия уникальности символов гарантирует и отсутствие конфликтов имён.
Символы активно используются внутри самого JavaScript, чтобы определять поведение объектов. Такие символы называются «хорошо известными» (well-known symbols).
Самый известный символ Symbol.iterator, который позволяет реализовать обход конструкции с помощью синтаксических конструкций for..of и спред-синтаксиса.
Шим — это любой фрагмент кода, который перехватывает обращение к API и добавляет уровень абстракции в приложение. Шимы существуют не только в вебе.
Полифилл — это шим для веба и браузерных API — специальный код (или плагин), который позволяет добавить некоторую функциональность в среду, которая эту функциональность по умолчанию не поддерживает (например, добавить новые функции JS в старые браузеры). Полифиллы не являются частью стандарта HTML5.
Транспиляция — преобразование кода, написанного в новом стандарте в его эквивалент в старом стиле (ES6 в ES5).
Как правило, для осуществления этой процедуры используется Babel.
const a = ["a", "b", "c"];
a.forEach(function (entry) {
console.log(entry);
});Может применяться как с инкрементом, так и с декрементом.
for (let i = 0; i < a.length; i += 1) {
console.log(a[i]);
}Вопреки распространенному заблуждению цикл for...in перебирает не индексы массива, а перечисляемые (iterable) свойства объекта.
Тем не менее, в некоторых случаях, таких как перебор разреженных массивов, for...in может оказаться полезным, если только соблюдать при этом меры предосторожности:
// a — разреженный массив
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (var key in a) {
if (
a.hasOwnProperty(key) &&
/^0$|^[1-9]\d*$/.test(key) &&
key <= 4294967294
) {
console.log(a[key]);
}
}Оператор for (переменная) of (сущность) позволяет пройти в цикле по свойствам сущности. Оператор работает только с итерируемыми сущностями.
let iterable = [10, 20, 30];
for (let value of iterable) {
value += 1;
console.log(value);
}
// 11
// 21
// 31true, если для каждого элемента массива колбек возвращает значение приводимое к true.true, если хотя бы для одного элемента массива колбек возвращает значение приводимое к true.Array.prototype.at()Array.prototype.concat()Array.prototype.copyWithin()Array.prototype.entries()Array.prototype.every()Array.prototype.fill()Array.prototype.filter()Array.prototype.find()Array.prototype.findIndex()Array.prototype.findLast()Array.prototype.findLastIndex()Array.prototype.flat()Array.prototype.flatMap()Array.prototype.forEach()Array.prototype.includes()Array.prototype.indexOf()Array.prototype.join()Array.prototype.keys()Array.prototype.lastIndexOf()Array.prototype.map()Array.prototype.pop()Array.prototype.push()Array.prototype.reduce()Array.prototype.reduceRight()Array.prototype.reverse()Array.prototype.shift()Array.prototype.slice()Array.prototype.some()Array.prototype.sort()Array.prototype.splice()Array.prototype.toLocaleString()Array.prototype.toString()Array.prototype.unshift()Array.prototype.values()Столько хватит? Есть ещё.
let arr = [1, 2, 3];
arr = [];Этот способ можно использовать, если ваш код никак не ссылается на оригинальный массив, так как при этом создается совершенно новый объект. Если в какой-то переменной есть ссылка на arr, то она не изменится.
let arrayList = ["a", "b", "c", "d", "e", "f"];
let anotherArrayList = arrayList;
arrayList = [];
console.log(anotherArrayList); // ['a', 'b', 'c', 'd', 'e', 'f']let arr = [1, 2, 3];
arr.length = 0;Существующий массив очистится, так же как и все ссылки на него.
let arrayList = ["a", "b", "c", "d", "e", "f"];
let anotherArrayList = arrayList;
arrayList.length = 0;
console.log(anotherArrayList); // []let arr = [1, 2, 3];
arr.splice(0, arr.length);let arr = [1, 2, 3];
while (arr.length) {
arr.pop();
}Цикл событий — это механизм, на каждом тике выполняющий функции из стека вызовов и, если он оказывается пустым, перемещающий задачи из очереди задач в стек вызовов для выполнения. Лежит в основе функционирования любого JavaScript-движка.
Стек вызовов — это структура данных (первым вошел, последним вышел), используемая для отслеживания порядка выполнения функций в текущем контексте (области видимости).
Очередь задач — это структура данных (первым вошел, первым вышел), используемая для отслеживания выполнения асинхронных функций, готовых оказаться в стеке вызовов.
Асинхронные операции делятся на два типа очередей:
Microtask (очередь микрозадач) — выполняется после текущей синхронной функции, но до следующей macrotask:
Promise.then/catch/finallyqueueMicrotask()MutationObserverasync/await (часть после await)Macrotask (очередь макрозадач) — выполняется после завершения текущей microtask-очереди:
setTimeout, setIntervalsetImmediate (Node.js)I/O (чтение файлов, сети)UI-события (клики, нажатия клавиш)MessageChannelconsole.log("1"); // Синхронная — сразу
setTimeout(() => console.log("2"), 0); // Macrotask
Promise.resolve().then(() => console.log("3")); // Microtask
queueMicrotask(() => console.log("4")); // Microtask
console.log("5"); // Синхронная — сразуВывод: 1, 5, 3, 4, 2
Правило: все синхронные операции → все microtask → одна macrotask → все microtask → следующая macrotask и так далее.
ECMAScript 5 (ES5) — 5-е издание ECMAScript, выпущенное в 2009 году. Поддерживается всеми современными браузерами полностью.
ECMAScript 6 (ECMAScript 2015, ES6) — 6-е издание ECMAScript, выпущенное в 2015 году. Поддерживается всеми современными браузерами полностью.
Несколько ключевых отличий двух стандартов:
// В новом стандарте можно сделать так:
const greetings = (name) => {
return `hello ${name}`;
};
// и даже так:
const greetings = (name) => `hello ${name}`;Константы в JavaScript отличаются от констант в других языках программирования. Они сохраняют неизменной только ссылку на значение. Таким образом, вы можете добавлять, удалять и изменять свойства объявленного константным объекта, но не можете перезаписать текущую переменную, в которой лежит этот объект.
const NAMES = [];
NAMES.push("Jim");
console.log(NAMES.length === 1); // true
NAMES = ["Steve", "John"]; // errorПеременные, объявленные с помощью новых ключевых слов let и const имеют блочную область видимости, то есть недоступны за пределами {}-блоков. Кроме того, они не поднимаются, как var-переменные.
Теперь функцию можно инициализировать с дефолтным значением параметров:
function multiply(a, b = 2) {
return a * b;
}
multiply(5); // 10Новый стандарт ввел в язык поддержку привычного синтаксиса классов (class), конструкторы (constructor) и ключевое слово extends для оформления наследования.
const obj1 = { a: 1, b: 2 };
const obj2 = { a: 2, c: 3, d: 4 };
const obj3 = { ...obj1, ...obj2 };Механизм для обработки результатов и ошибок асинхронных операций. Промисы можно чейнить (объединять в цепочки).
const isGreater = (a, b) => {
return new Promise((resolve, reject) => {
if (a > b) {
resolve(true);
} else {
reject(false);
}
});
};
isGreater(1, 2)
.then((result) => {
console.log("greater");
})
.catch((result) => {
console.log("smaller");
});import myModule from "./myModule";
const myModule = {
x: 1,
y: () => {
console.log("This is ES5");
},
};
export default myModule;Действительно, в JavaScript 0.1 + 0.2 на самом деле равно 0.30000000000000004.
Дело в том, что все числа в языке (даже целые) представлены в формате с плавающей запятой (float). В двоичной системе счисления эти числа — бесконечные дроби. Для их хранения выделяется ограниченный объем памяти, поэтому возникают подобные неточности.
let a = {
a: 1,
};
let b = {
a: 1,
};
let c = a;
console.log(a === b); // false
console.log(a === c); // trueВ JS объекты и примитивы сравниваются по-разному. Примитивы сравниваются по значению. Объекты — по ссылке или адресу в памяти, где хранится переменная.
Вот почему первый console.log возвращает false, а второй — true. Переменные a и c ссылаются на один объект, а переменные a и b — на разные объекты с одинаковыми свойствами и значениями.
Как итог: сравнивая две переменных-объекта с помощью оператора сравнения результат будет равен true только в том случае, если обе переменных ссылаются на один и тот же объект.
В JavaScript есть два основных способа проверить наличие свойства в объекте — метод hasOwnProperty и оператор in.
Метод hasOwnProperty() возвращает true, если указанное свойство является прямым свойством объекта, и false в противном случае. Этот метод не проверяет свойства в цепочке прототипов объекта.
Оператор in возвращает true, если указанное свойство существует в объекте, независимо от того, является ли оно собственным свойством или унаследовано:
const myObject = {
test: "hello",
};
console.log("toString" in myObject); // true
console.log("test" in myObject); // true
console.log(myObject.hasOwnProperty("toString")); // false
console.log(myObject.hasOwnProperty("test")); // trueДля такой проверки можно использовать встроенный метод Array.isArray().
Он принимает объект в качестве аргумента и возвращает true, если объект является массивом, и false в противном случае:
let arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true
let obj = { a: 1, b: 2 };
console.log(Array.isArray(obj)); // falseПримитивы строка, число и boolean имеют свойства и методы, несмотря на то, что они не являются объектами:
let name = "marko";
console.log(typeof name); // string
console.log(name.toUpperCase()); // MARKOname — это строка (примитивный тип), у которого нет свойств и методов, но когда мы вызываем метод toUpperCase(), это приводит не к ошибке, а к «MARKO».
Причина такого поведения заключается в том, что name в момент обращения к его свойству временно преобразуется в объект. У каждого примитива кроме null и undefined есть объект-обертка. Такими объектами являются String, Number, Boolean, Symbol и BigInt. В нашем случае код принимает следующий вид:
console.log(new String(name).toUpperCase()); // MARKOВременный объект отбрасывается по завершении работы со свойством или методом.
?.Оператор ?. позволяет безопасно обращаться к вложенным свойствам объекта. Если какое-то свойство в цепочке равно null или undefined, выражение вернёт undefined вместо ошибки:
const user = {
address: {
street: "ул. Пушкина",
},
};
// Без optional chaining
const street = user && user.address && user.address.street; // 'ул. Пушкина'
const zip = user && user.address && user.address.zip; // undefined
// С optional chaining
const street2 = user?.address?.street; // 'ул. Пушкина'
const zip2 = user?.address?.zip; // undefined?. работает с свойствами, методами и элементами массивов:
user?.name; // обращение к свойству
user?.getName(); // вызов метода
arr?.[0]; // обращение к элементу массива??Оператор ?? возвращает правый операнд, если левый равен null или undefined (но не 0, "" или false):
const name = null ?? "Гость"; // 'Гость'
const name2 = "Алекс" ?? "Гость"; // 'Алекс'
// Отличие от ||:
const count = 0 ?? 10; // 0 — 0 не null/undefined, поэтому остаётся
const count2 = 0 || 10; // 10 — || считает 0 ложным значениемЭто делает ?? удобным для дефолтных значений, когда 0, "" или false — валидные данные.
Эти методы часто путают из-за похожих названий, но работают они по-разному:
slice | splice | |
|---|---|---|
| Изменяет исходный массив | Нет | Да |
| Возвращает | Новый массив с вырезанными элементами | Массив удалённых элементов |
| Синтаксис | slice(start, end) | splice(start, deleteCount, ...items) |
const arr = [1, 2, 3, 4, 5];
// slice — не меняет исходный массив
const sliced = arr.slice(1, 3);
console.log(sliced); // [2, 3]
console.log(arr); // [1, 2, 3, 4, 5]
// splice — меняет исходный массив
const removed = arr.splice(1, 2, 6, 7);
console.log(removed); // [2, 3]
console.log(arr); // [1, 6, 7, 4, 5]Запомните: slice — «нарезает» (без побочных эффектов), splice — «вырезает и вставляет» (мутирует массив).
Эта подборка охватывает основные темы, которые часто встречаются на собеседованиях фронтенд-разработчиков. Но знание теории — это только половина успеха. Важно уметь применять эти знания на практике.
Попробуйте решить задачи на Девстанции — интерактивной платформе для прокачки навыков программирования с элементами RPG. Здесь вы найдёте паутину задач по JavaScript с тестами, бонусными условиями, AI-ревью кода и рейтингами. Каждая задача — это возможность проверить свои знания на практике.