❓👨💻 Вопросы для подготовки к собеседованию по JavaScript. Часть 1
Стрелочные функции, замыкания, промисы и async/await, методы работы с массивами и объектами, шаблонные литералы, особенности Map и Set, оператор расширения и клонирование объектов.
1. Какие типы данных есть в JavaScript?
Number – число. Тип Number в JavaScript может хранить как целые числа, так и числа с плавающей точкой.
BigInt – используется для представления целых чисел произвольной длины, превышающих 2^53 – 1. BigInt создается с помощью добавления n в конец целочисленного литерала или путем вызова функции BigInt(), которая создает BigInt из строк, чисел и т.д.:
// Создание BigInt путем добавления n в конец целочисленного литерала const bigInt1 = 1234567890123456789012345678901234567890n; // Создание BigInt путем вызова функции BigInt() const bigInt2 = BigInt("1234567890123456789012345678901234567890"); // Создание BigInt из числа const bigInt3 = BigInt(10); // Это то же самое, что и 10n // Сложение console.log(1n + 2n); // 3n // Деление console.log(5n / 2n); // 2n
String – строка (последовательность символов), например, "JavaScript – главный язык интернета"
.Строки записываются с использованием кавычек, можно использовать одинарные или двойные кавычки.
Boolean – логический (булев) тип, который может принимать
значения true
(истина) или false
(ложь).
Object – объект. Это значение в памяти, на которое возможно сослаться с помощью идентификатора. Объект может расцениваться как набор свойств. Значения свойств могут иметь любой тип, включая другие объекты, что позволяет строить сложные, разветвленные иерархии данных.
null – специальное значение, которое представляет «ничего», «пусто», или «неизвестное значение».
undefined – это значение присваивается переменной, если она была объявлена, но не получила значения.
Symbol – это уникальный и неизменяемый тип данных, который можно использовать в качестве идентификатора для свойств объекта.
2. В чем состоит различие между == и ===?
В JavaScript операторы ==
и ===
используются для сравнения
двух значений, но они работают по-разному:
==
проверяет на абстрактное равенство, то есть он
преобразует типы данных перед сравнением – например, если вы сравниваете строку
с числом, JavaScript преобразует строку в число перед сравнением. Если строка
не может быть преобразована в число, она преобразуется в NaN, что возвращает
false
. Если оба операнда имеют разные типы данных, но они могут быть
преобразованы в один и тот же тип данных и имеют одно и то же значение,
оператор ==
вернет true
:
var a = 10; console.log(a == 20); // false console.log(a == "1o"); // false console.log(a == "10"); // true
===
проверяет на
строгое равенство, то есть он не выполняет преобразование типов данных. Если два значения имеют разные типы данных, оператор ===
вернет
false
, даже если они имеют одно и то же значение. Если оба операнда имеют одинаковый тип данных и одинаковое значение, оператор ===
вернет true
:
var x = 10; console.log(x === "10"); // false console.log(x === 10); // true
3. Какие способы объявления переменных есть в JavaScript?
В JavaScript есть четыре способа объявления переменных:
myVariable = 5; var myVariable = 5; let myVariable = 5; const myVariable = 5;
myVariable = 5;
– неявное объявление переменной. Оно создает
глобальную переменную myVariable и присваивает ей значение 5, что может привести
к ошибкам в строгом режиме:
"use strict"; myVariable = 5; // ReferenceError: myVariable is not defined
var myVariable = 5;
– явное
объявление переменной с использованием ключевого слова var
. Область видимости переменной
myVariable может быть функциональной или глобальной, если она объявлена вне
функции. Недостаток var
состоит в том, что ее область видимости не ограничивается
блоком, в котором используется переменная:
function example() { var myVariable = 1; if (true) { var myVariable = 2; // Переопределяет myVariable из внешнего блока console.log(myVariable); // Выводит 2 } console.log(myVariable); // Выводит 2 } example();
let myVariable
= 5;
– явное объявление переменной с использованием ключевого слова let
. Область
видимости такой переменной ограничивается блоком, в котором она объявлена – на уровне
функции она не видна:
function example() { let myVariable = 5; if (true) { let myVariable = 25; // Это новая переменная myVariable, которая видна только внутри этого блока console.log(myVariable); // Выводит 25 } console.log(myVariable); // Выводит 5 } example();
const myVariable
= 5;
– это явное объявление переменной с использованием ключевого слова const
.
Переменная myVariable объявляется в области видимости блока, как и let
. Однако,
в отличие от let
, переменная myVariable является неизменяемой – ее значение не
может быть изменено после объявления, за одним исключением: если значение
является объектом или массивом, его свойства или элементы могут быть изменены,
но сама переменная все равно остается неизменяемой:
const myVariable = 5; console.log(myVariable); // Выводит 5 myVariable = 10; // TypeError: Assignment to constant variable.
const myObject = { key: "value" }; myObject.key = "otherValue"; // Это допустимо console.log(myObject.key); // Выводит "otherValue" const myArray = []; myArray.push("A"); // Это допустимо console.log(myArray); // Выводит ["A"]
4. В чем разница между null и undefined?
В JavaScript и null, и undefined представляют отсутствие значения, но они используются в разных контекстах и имеют разные семантические значения.
undefined
– присваивается переменной, когда она объявлена, но ей не
присвоено конкретное значение:
var testVar; console.log(testVar); // Выводит undefined console.log(typeof testVar); // Выводит undefined
null
– специальное значение, которое представляет «ничего», «пусто» или «неизвестное
значение». Присваивается переменной вручную, чтобы указать, что она не должна
иметь значения. Например, если нужно очистить значение переменной, можно установить
его в null
:
var testVar = 5; console.log(testVar); // Выводит 5 var testVar = null; console.log(testVar); // Выводит null console.log(typeof testVar); // Выводит object
5. Чем стрелочные функции отличаются от обычных?
Стрелочные функции позволяют использовать упрощенный синтаксис при создании небольших функций-обработчиков. У них есть некоторые ограничения по сравнению с обычными функциями:
Стрелочные функции не могут использовать объект arguments. В обычных функциях этот объект содержит все переданные при вызове аргументы:
function sum() { console.log(arguments); // { '0': 1, '1': 2, '2': 3 } } sum(1, 2, 3); const sumArrow = () => { console.log(arguments); // ReferenceError: arguments is not defined } sumArrow(1, 2, 3);
У стрелочных функций другой синтаксис записи. Они
записываются короче, используя стрелку =>
и не требуя ключевого слова
function
:
function sum(a, b) { return a + b; } const sumArrow = (a, b) => a + b;
У стрелочных функций нет собственного контекста this. Вместо этого контекст берется из внешней области видимости:
const myObject = { text: "JavaScript - очень простой язык", description: function() { setTimeout(() => { console.log(this.text); }, 1000); } }; myObject.description(); // Выводит "JavaScript - очень простой язык"
Стрелочные функции нельзя использовать как конструкторы с
ключевым словом new. То есть, из них нельзя создавать объекты при помощи
оператора new
:
const myFunction = () => {}; const newFunction = new myFunction(); // TypeError: myFunction is not a constructor
6. Что такое замыкание?
Замыкание (closure) в JavaScript – это комбинация функции и лексического окружения, в котором эта функция была определена. Такая функция имеет доступ к переменным внешней функции, даже после того, как внешняя функция завершила выполнение:
function createAdder(x) { return function(y) { return x + y; }; } const add5 = createAdder(5); const add10 = createAdder(10); console.log(add5(2)); // Выводит 7 console.log(add10(2)); // Выводит 12
7. Что такое шаблонные строки (литералы)?
Шаблонные строки (template literals) в JavaScript – это
новый способ работы со строками, введенный в ECMAScript 6 (ES6). Они обозначаются
обратными кавычками `
вместо одинарных или двойных кавычек. Шаблонные строки
позволяют создавать многострочные строки без необходимости использования
специальных символов или конкатенации строк:
let multilineString = `Это строка номер 1 Это строка номер 2 Это строка номер 3`; console.log(multilineString);
Шаблонные строки также поддерживают интерполяцию строк, что
позволяет вставлять выражения прямо в строку. Эти выражения заключаются в
фигурные скобки ${expression}
и вычисляются при создании строки:
let name = "Василий Пупкин"; let dailyBonus = 0; let greeting = `Привет, ${name}! Тебе начислен ежедневный бонус - ${dailyBonus + 5} руб`; console.log(greeting); // Выводит: Привет, Василий Пупкин! Тебе начислен ежедневный бонус - 5 руб.
8. Что такое Map и Set в JavaScript?
Map и Set – это два типа коллекций, которые были введены в ECMAScript 6 (ES6). Они предоставляют более гибкие и мощные способы работы с наборами данных по сравнению с обычными объектами и массивами.
Map – это коллекция, которая состоит из пар ключ-значение, подобно объектам. Основное отличие Map от объектов заключается в том, что Map запоминает порядок добавления пар и позволяет использовать в качестве ключей данные любых типов:
let user1 = { name: 'Егор' }; let user2 = { name: 'Марина' }; let userRoles = new Map(); userRoles.set(user1, 'администратор'); userRoles.set(user2, 'редактор'); console.log(userRoles.get(user1)); // Выводит: администратор console.log(userRoles.get(user2)); // Выводит: редактор
При желании в Map в качестве ключей можно использовать функции:
function sayHello() { console.log('приветствие'); } function sayGoodbye() { console.log('прощание'); } let functionMap = new Map(); functionMap.set(sayHello, 'Привет!'); functionMap.set(sayGoodbye, 'До свидания!'); console.log(functionMap.get(sayHello)); // Выводит: Привет! console.log(functionMap.get(sayGoodbye)); // Выводит: До свидания!
Set – это множество, в котором каждое значение может появляться только один раз. Дубликатов в Set нет:
const mySet = new Set(); mySet.add('яблоко'); mySet.add('апельсин'); mySet.add('апельсин'); // Это не добавит 'апельсин' второй раз, так как такое значение уже есть console.log(mySet.has('яблоко')); // Выводит: true console.log(mySet.has('апельсин')); // Выводит: true console.log(mySet.has('банан')); // Выводит: false
9. Как проверить наличие свойства в объекте?
В JavaScript есть два основных способа проверить наличие
свойства в объекте – метод hasOwnProperty
и оператор in
.
Метод hasOwnProperty()
возвращает true, если указанное
свойство является прямым свойством объекта, и false в противном случае. Этот
метод не проверяет свойства в цепочке прототипов объекта. Оператор in
возвращает true, если указанное свойство существует в объекте, независимо от
того, является ли оно собственным свойством или унаследовано:
const book = { title: "Великий Гэтсби", author: "Ф. Скотт Фицджеральд", year: 1925 }; console.log(book.hasOwnProperty("title")); // true console.log("title" in book); // true console.log("year" in book); // true console.log("language" in book); // false
10. Как получить доступ к свойствам объекта?
В JavaScript есть два основных способа доступа к свойствам объекта: статический (с использованием точечной нотации) и динамический (с использованием квадратных скобок).
Точечная нотация позволяет напрямую получить доступ к свойству объекта, используя имя свойства. Скобочная нотация позволяет динамически получить доступ к свойству объекта с использованием квадратных скобок:
const movie = { title: "Крестный отец", director: "Фрэнсис Форд Коппола", year: 1972 }; console.log(movie['title']); // Крестный отец console.log(movie.director); // Фрэнсис Форд Коппола
Скобочная нотация использует интерполяцию и особенно полезна, если имя свойства неизвестно заранее или когда оно хранится в переменной:
var propertyName = "title"; var book = { title: "Анна Каренина", author: "Лев Толстой", year: 1877 }; console.log(book[propertyName]); // Анна Каренина
11. Какие основные методы работы с массивами есть в JavaScript?
Oсновные методы для работы с массивами – forEach, filter, map и reduce.
Метод forEach
выполняет функцию для каждого элемента в
массиве. Он не возвращает ничего, но позволяет выполнять действия с каждым
элементом массива. Применяется, когда нужно выполнить некоторые операции над
каждым элементом, но не нужно создавать новый массив:
const array = ['один', 'два', 'три']; array.forEach(element => console.log(element)); /* вывод: один два три */
Метод filter
создает новый массив, включающий только те
элементы исходного массива, для которых функция обратного вызова возвращает
true. Используется, когда нужно отфильтровать массив, чтобы включить только
определенные элементы:
const numbers = [1, 2, 3, 4, 5]; const evenNumbers = numbers.filter(number => number % 2 === 0); console.log(evenNumbers); // [2, 4]
Метод map
создает новый массив, который состоит из
результатов применения функции к каждому элементу исходного массива. Применяется,
когда нужно преобразовать каждый элемент массива:
const numbers = [1, 2, 3, 4, 5]; const squares = numbers.map(number => number * number); console.log(squares); // [1, 4, 9, 16, 25]
Метод reduce
выполняет функцию для каждого элемента массива,
накапливая результат в одном значении. Используется, когда нужно объединить все
элементы массива в одно значение, например, вычислить сумму всех чисел в
массиве:
const numbers = [1, 2, 3, 4, 5]; const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); console.log(sum); // 15
12. Какие способы создания объектов есть в JavaScript?
Объекты
JavaScript создаются с помощью функции-конструктора, литеральной
нотации объекта, класса и метода Object.create()
.
Функция-конструктор – это специальная функция, которую можно
использовать для создания объектов с определенными свойствами и методами.
Функция-конструктор используется с ключевым словом new
:
function Movie(title, director, year) { this.title = title; this.director = director; this.year = year; } const movie = new Movie('Дракула Брэма Стокера', 'Фрэнсис Форд Коппола', 1992); console.log(movie); // { title: 'Дракула Брэма Стокера', director: 'Фрэнсис Форд Коппола', year: 1992 }
Литеральная нотация объекта позволяет создать объект, указав
его свойства и значения внутри фигурных скобок {}
:
const movie = { title: 'Сердце Ангела', director: 'Алан Паркер', year: 1987 }; console.log(movie); // { title: 'Сердце Ангела', director: 'Алан Паркер', year: 1987 }
Классы позволяют создавать объекты с помощью синтаксиса, похожего на классы в других языках программирования:
class Movie { constructor(title, director, year) { this.title = title; this.director = director; this.year = year; } } const movie = new Movie('Шоссе в никуда', 'Дэвид Линч', 1997); console.log(movie); // { title: 'Шоссе в никуда', director: 'Дэвид Линч', year: 1997 }
Метод Object.create() позволяет создать новый объект, используя существующий объект в качестве прототипа для нового объекта. Этот метод принимает два аргумента: прототип и объект свойств. Объект свойств определяет свойства нового объекта и их атрибуты configurable
, enumerable
, writable
и value
:
const moviePrototype = { title: 'Интерстеллар', director: 'Кристoфер Нолан', year: 2014 }; const movie = Object.create(moviePrototype, { title: { value: 'Интерстеллар', writable: true, enumerable: true, configurable: true }, director: { value: 'Кристoфер Нолан', writable: true, enumerable: true, configurable: true }, year: { value: 2014, writable: true, enumerable: true, configurable: true } }); console.log(movie); // { title: 'Интерстеллар', director: 'Кристoфер Нолан', year: 2014 }
13. Что такое Promise (промис)?
Промис (Promise) — специальный объект JavaScript, который используется для написания и обработки асинхронного кода. Он имеет три состояния:
- pending – начальное состояние, означает, что асинхронная операция еще не завершена.
- fulfilled – операция успешно завершена.
- rejected – операция завершена с ошибкой.
Промисы создаются с помощью конструктора new Promise()
. Этот
конструктор принимает в качестве аргумента функцию, которая выполняет
асинхронную операцию. Функция принимает два аргумента resolve
и reject
, которые
используются для изменения состояния промиса. Если асинхронная операция
завершена успешно, вызывается resolve
, если произошла ошибка, вызывается reject
:
let promise = new Promise((resolve, reject) => { // асинхронная операция setTimeout(() => { resolve("Результат асинхронной операции"); }, 1000); });
Промисы позволяют обрабатывать результаты асинхронных операций, используя методы .then()
и .catch()
. Метод .then()
принимает два аргумента: функцию обратного вызова, которая будет вызвана при успешном выполнении промиса, и функцию обратного вызова, которая будет вызвана при ошибке. Метод .catch()
используется для обработки ошибок, которые могут произойти при выполнении промиса:
let promise = new Promise((resolve, reject) => { // асинхронная операция setTimeout(() => { resolve("Результат асинхронной операции"); }, 1000); }); promise .then(result => { console.log("Результат: " + result); }) .catch(error => { console.log("Ошибка: " + error); });
Промисы можно связывать в цепочки, что позволяет
выполнять несколько асинхронных операций последовательно. Для этого результат
каждого промиса передается в следующий промис в цепочке. Это делается с помощью
метода .then()
:
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("Результат первой операции"); }, 1000); }); let promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("Результат второй операции"); }, 2000); }); promise1 .then(result1 => { console.log("Результат первой операции: " + result1); return promise2; }) .then(result2 => { console.log("Результат второй операции: " + result2); });
14. Что такое async/await и как они используются?
Async/await – это синтаксис JavaScript, который облегчает
работу с промисами. Ключевое слово async
перед функцией означает, что функция
всегда возвращает промис. Ключевое слово await
используется внутри асинхронных
функций и заставляет JavaScript ожидать, пока промис не будет выполнен, прежде
чем продолжить выполнение кода:
async function example() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("Сделано!"), 2000) }); let result = await promise; // жди выполнения промиса alert(result); // Сделано! появляется в подтверждении действия } example();
Надо отметить, что await
нельзя использовать вне асинхронной
функции. Например, этот код приведет к ошибке:
async function fetchDataFromApi() { const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single'); const json = await res.json(); console.log(json.joke); } await fetchDataFromApi(); // SyntaxError: await is only valid in async functions
Чтобы решить эту проблему, можно обернуть вызов в другую асинхронную функцию:
async function fetchDataFromApi() { const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single'); const json = await res.json(); console.log(json.joke); } async function init() { await fetchDataFromApi(); console.log('Принес новый анекдот!'); } init();
15. Как проверить, является ли объект массивом?
Для такой проверки можно использовать встроенный метод
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
16. Что делает оператор расширения?
Оператор расширения ...
разворачивает итерируемые элементы в
отдельные элементы, что удобно для передачи аргументов, объединения
массивов/объектов и добавления новых свойств в объекты. Используется:
В функциях, где ожидаемое количество аргументов для вызова равно нулю или более:
function sum(a, b, c) { return a + b + c; } let numbers = [1, 2, 3]; console.log(sum(...numbers)); // 6
В литералах массива:
let arr1 = [1, 2, 3]; let arr2 = [...arr1, 4, 5]; console.log(arr2); // [1, 2, 3, 4, 5]
В литералах объекта, где количество пар ключ-значение должно быть равно нулю или более:
let arr1 = [1, 2, 3]; let arr2 = [4, 5, 6]; let combined = [...arr1, ...arr2]; console.log(combined); // [1, 2, 3, 4, 5, 6] let obj1 = { a: 1, b: 2 }; let obj2 = { c: 3, d: 4 }; let combinedObj = { ...obj1, ...obj2 }; console.log(combinedObj);// { a: 1, b: 2, c: 3, d: 4 }
Для преобразования строки в массив символов:
const str = "JavaScript"; const charArray = [...str]; console.log(charArray); // ['J', 'a', 'v', 'a', 'S', 'c', 'r', 'i', 'p', 't']
Для преобразования числа в массив цифр и наоборот:
const num = 12345; const numArray = [...num.toString()].map(Number); console.log(numArray); // [1, 2, 3, 4, 5] const number = Number(numArray.join('')); console.log(number); // 12345
Для копирования объектов:
let obj = { name: 'Марк', age: 25 }; let newobj = { ...obj }; console.log(newobj); // {name: 'Марк', age: 25}
17. Как выполняется клонирование объекта?
Если объект не содержит вложенных объектов, как в
приведенном ниже примере, для клонирования можно использовать оператор
расширения ...
или метод Object.assign()
:
const obj = { firstName: 'Василий', lastName: 'Пупкин' }; const copy1 = {...obj}; console.log(copy1); // {firstName: 'Василий', lastName: 'Пупкин'} // или const copy2 = Object.assign({}, obj); console.log(copy2); // {firstName: 'Василий', lastName: 'Пупкин'}
Если объект содержит вложенные объекты, нужно выполнить глубокое копирование. Относительно медленный вариант – с использованием JSON:
const obj = { data: { id: 1 } }; const copy = JSON.parse(JSON.stringify(obj));
Другой вариант – с использованием метода cloneDeep
из библиотеки lodash:
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script> <script> const original = { name: 'Максим', details: { age: 30 } }; // Вызываем метод Lodash напрямую const copy = _.cloneDeep(original); copy.name = 'Марина'; copy.details.age = 32; console.log(original); console.log(copy); </script>
18. Как изменить контекст функции?
Изменить контекст функции можно с помощью методов bind()
,call()
и apply()
.
Метод bind()
возвращает новую функцию с привязанным контекстом:
function myFunction() { return this; } const obj = {name: 'Лев Толстой'}; const newFunction = myFunction.bind(obj); console.log(newFunction()); // {name: 'Лев Толстой'}
Метод call()
принимает последовательность аргументов, а apply()
принимает массив аргументов в качестве второго параметра:
function sum(a, b) { return a + b; } const numbers = [1, 2]; console.log(sum.call(null, 1, 2)); // 3 console.log(sum.apply(null, numbers)); // 3
19. Что такое тернарный оператор и как он работает?
Тернарный оператор – это сокращенная форма записи if-else. Он называется тернарным, потому что является единственным оператором в JavaScript, который принимает три аргумента. Синтаксис тернарного оператора:
условие ? выражение_если_истинно : выражение_если_ложно
Условие – любое условие, которое возвращает true
или false
.
Выражение для истинного условия – что нужно вернуть, если условие истинно.
Выражение для ложного условия – что нужно вернуть, если условие ложно.
Например:
let accessAllowed; let age = 16; accessAllowed = (age > 18) ? "Доступ открыт" : "Доступ закрыт"; // аналогично записи через if-else: if(age > 18) { accessAllowed = "Доступ открыт"; } else { accessAllowed = "Доступ закрыт"; } console.log(accessAllowed); // выведет Доступ закрыт
20. Что такое деструктуризация?
Деструктуризация в 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); // 30
Продолжение следует.