- PVSM.RU - https://www.pvsm.ru -
Хотите подготовиться к собеседованию по JavaScript и ищете вопросы, на которых можно попрактиковаться? Если так — считайте, что ваши поиски окончены. Автор материала, перевод которого мы сегодня публикуем, говорит, что собрал более двух десятков вопросов по JavaScript, предназначенных для тех, кто хочет превратиться из джуниора в сеньора, для тех, кто стремится успешно пройти собеседование в сфере фронтенд-разработки и получить интересное предложение от работодателя.
В JavaScript есть два оператора для проверки равенства величин. Первый — это так называемый оператор строгого равенства. Второй — оператор нестрогого равенства, при использовании которого может производиться преобразование типов проверяемых величин.
===) проверяет значения на равенство, не выполняя при этом преобразования типов.==) проверяет значения на равенство, выполняя их приведение к общему типу.var a = "42";
var b = 42;
a == b; // true
a === b; // false
Вот некоторые правила, касающиеся использования различных операторов проверки равенства в JavaScript:
true или false — постарайтесь избегать оператора ==. Используйте оператор ===.=== в том случае, если работаете со следующими значениями: 0, «» или [] (пустой массив).==. Причём, это не только безопасно, но и способствует упрощению кода и улучшению его читабельности.→ Источник [2]
Суть этого вопроса в том, чтобы выяснить, какие значения, в случае преобразования их к логическому типу, превращаются в false, а какие — в true.
Вот список значений, которые можно назвать «ложными» (falsy). Они, при преобразовании к логическому типу, превращаются в значение false:
«» (пустая строка).0, -0, NaN (не-число).null, undefined.
«Ложным» является и логическое значение false.
Любое значение, которое не входит в этот список, при его преобразовании к логическому типу, превращается в true (такие значения называют «истинными» — truthy). Например:
«hello».42.[ ], [ 1, «2», 3 ] (массивы).{ }, { a: 42 } (объекты).function foo() { .. } (функции).
«Истинным» является и логическое значение true.
→ Источник [2]
IIFE (Immediately Invoked Function Expression) — это немедленно вызываемое функциональное выражение. Такое выражение выполняется немедленно после создания.
(function IIFE(){
console.log( "Hello!" );
})();
// "Hello!"
Этот паттерн часто используется для того чтобы не допустить загрязнения глобального пространства имён. Дело в том, что переменные, объявленные в IIFE (как и в любой другой обычной функции), невидимы за пределами этой функции.
→ Источник [3]
Вот простые правила по использованию различных способов объявления функций, которыми я руководствуюсь, разрабатывая код для сред, поддерживающих стандарт ES6 и более новые стандарты:
function в глобальной области видимости и для свойств Object.prototype.function для конструкторов объектов.Как видите, стрелочные функции рекомендуется использовать практически везде. У такого положения дел есть несколько причин:
this окружающего контекста, не имея собственного this. Если такие функции применяются последовательно, без использования обычных функций в сложных конструкциях, это обеспечивает безопасную работу с контекстом.this.→ Источник [4]
Сначала рассмотрим примеры.
Функция-конструктор:
function Person(name) {
this.name = name;
}
ES6-класс:
class Person {
constructor(name) {
this.name = name;
}
}
Если речь идёт о создании простых объектов, то конструкторы и классы, используемые для этой цели, выглядят очень похоже.
Основная разница между конструкторами и классами проявляется при использовании наследования. Если нам нужно создать класс Student, являющийся подклассом класса Person, и добавить к этому новому классу поле studentId, то вот как будет выглядеть код, в котором используются конструкторы, и код, в котором применяются классы.
Функция-конструктор:
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;
}
}
→ Источник [5]
Процитируем MDN: «Метод bind() создаёт новую функцию, которая при вызове устанавливает в качестве контекста выполнения this предоставленное значение. В метод также передаётся набор аргументов, которые будут установлены перед переданными в привязанную функцию аргументами при её вызове».
Полагаю, что метод .bind() особенно полезен для привязки значения this в методах классов, которые нужно передавать в другие функции. Этот приём часто используется в React-компонентах.
→ Источник [5]
Анонимные функции используются при создании 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]
→ Источник [5]
Ключевое слово const и метод Object.freeze() — это совершенно разные вещи.
Ключевое слово const применяется к привязкам (к «переменным»). Оно создаёт иммутабельную привязку, то есть — к переменной (константе), объявленной с помощью ключевого слова const, нельзя привязать что-то новое. Константе нельзя присвоить новое значение.
const person = {
name: "Leonardo"
};
let animal = {
species: "snake"
};
person = animal; // Uncaught TypeError: Assignment to constant variable.
Метод Object.freeze() работает со значениями. А точнее — с объектными значениями. Он делает объект иммутабельным, что защищает от изменений значения свойств этого объекта.
let person = {
name: "Leonardo"
};
Object.freeze(person);
person.name = "Lima"; // Uncaught TypeError: Cannot assign to read only property 'name' of object
console.log(person);
Обратите внимание на то, что сообщение об ошибке выводится в строгом режиме. В обычном режиме операция изменения свойства «замороженного» объекта просто не срабатывает.
→ Источник [6]
Генераторы — это функции, из которых можно «выходить», и в которые можно «входить» по мере необходимости. Их контекст (привязки переменных) сохраняется между сеансами «входа» в них. Генераторы объявляют с использованием ключевого слова function*. Такая функция, при её первом вызове, не выполняет код, возвращая особый объект, генератор, который позволяет управлять её выполнением. Для получения очередного значения, выдаваемого генератором, нужно вызвать его метод next(). Благодаря этому выполняется код функции до тех пор, пока в нём не встретится ключевое слово yield, возвращающее значение.
Функцию-генератор можно вызывать сколько угодно раз. Каждый раз будет возвращаться новый генератор. Но каждый генератор можно обойти лишь один раз.
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
let iterationCount = 0;
for (let i = start; i < end; i += step) {
iterationCount++;
yield i;
}
return iterationCount;
}
→ Источник [7]
Если в двух словах описать основные полезные возможности генераторов, то окажется, что они заключаются в следующем:
Главное в генераторах — это то, что получить следующее значение, возвращаемое генератором, можно только тогда, когда оно нужно в коде, использующем генератор. Генераторы не возвращают всё за один раз. В некоторых ситуациях эта их особенность может оказаться весьма удобной.
→ Источник [8]
Сущность концепции «поднятия переменных» заключается в том, что объявления «поднимаются» в верхнюю часть текущей области видимости. В результате переменной можно воспользоваться до её объявления. Поднимаются лишь объявления переменных, но не код их инициализации. Обратите внимание на то, что поведение переменных, объявляемых с использованием ключевого слова var, отличается от поведения переменных и констант, объявленных с использованием let и const.
→ Источник [9]
var output = (function(x) {
delete x;
return x;
})(0);
console.log(output);
Этот код выведет 0. Оператор delete используется для удаления свойств объектов. А x — это не свойство объекта — это локальная переменная. Оператор delete не воздействует на локальные переменные.
→ Источник [10]
var Employee = {
company: 'xyz'
}
var emp1 = Object.create(Employee);
delete emp1.company
console.log(emp1.company);
Этот код выведет xyz. Свойство company является не свойством объекта emp1, а свойством его прототипа. Оператор delete не удаляет свойства прототипов объектов. У объекта emp1 нет собственного свойства company. Проверить это можно так:
console.log(emp1.hasOwnProperty('company')); // false
Если нам всё же необходимо удалить это свойство — сделать это можно, либо напрямую обратившись к объекту Employee (delete Employee.company), либо — обратившись к прототипу объекта emp1, воспользовавшись его свойством __proto__ (delete emp1.__proto__.company).
→ Источник [10]
Прототип (Prototype) — это порождающий шаблон проектирования. Он используется для создания объектов. Объекты, созданные с его помощью, содержат значения, скопированные из их прототипа (из объекта-образца). Этот шаблон ещё называют шаблоном Свойства (Properties).
Пример использования паттерна «прототип» — это инициализация неких объектов стандартными значениями, хранящимися в базе данных. Такие значения, записанные в прототип, копируются в новые объекты без обращения к базе данных.
Надо отметить, что этот паттерн редко используется в классических языках. В JavaScript применяется модель прототипного наследования. Данный паттерн применяется при конструировании новых объектов и их прототипов.
→ Источник [11]
В ES6 выполняется подъём переменных и констант, объявленных с использованием ключевых слов let и const (выполняется и подъём сущностей, объявленных с использованием ключевых слов var, class и function). Однако в коде имеется зона, простирающаяся от входа в область видимости до объявления переменной или константы. При обращении к переменной или константе в этой зоне будет выдана ошибка. Это и есть «временная мёртвая зона» (Temporal Dead Zone, TDZ).
//console.log(aLet) // выбросит ReferenceError
let aLet;
console.log(aLet); // undefined
aLet = 10;
console.log(aLet); // 10
В данном примере TDZ заканчивается после объявления aLet, но не после присвоения aLet значения.
→ Источник [12]
Для того чтобы понять разницу между этими методами — поговорим об особенностях работы каждого из них.
Вот как работает .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().
→ Источник [5]
Необъявленная переменная создаётся при назначении значения идентификатору, который не был ранее объявлен с использованием var, let или const. Необъявленные переменные объявляются в глобальной области видимости, за пределами текущей области видимости. В строгом режиме при попытке назначения значения необъявленной переменной будет выброшено исключение ReferenceError. Использовать необъявленные переменные не рекомендуется — так же, как не рекомендуется использовать глобальные переменные. Их стоит всеми силами избегать. Для того чтобы обезопасить себя от последствий использования необъявленных переменных, воспользуйтесь блоком try/catch.
function foo() {
x = 1; // Выбрасывает в строгом режиме ReferenceError
}
foo();
console.log(x); // 1
Переменная, содержащая undefined — это объявленная переменная, которой не назначено некое значение. Значение undefined образует собственный тип данных. Если функция ничего не возвращает, и при этом результат её вызова записывается в переменную, то в эту переменную попадёт undefined. Для того чтобы организовать проверку на undefined, можно воспользоваться оператором строгого равенства (===) или оператором typeof, который возвратит строку undefined. Обратите внимание на то, что при проверке на undefined не следует пользоваться оператором нестрогого равенства (==), так как он считает равными значения undefined и null.
var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === 'undefined'); // true
console.log(foo == null); // true. Не используйте такую конструкцию для проверки на undefined!
function bar() {}
var baz = bar();
console.log(baz); // undefined
Переменная, содержащая значение null, должна быть явным образом установлена в это значение. Она символизирует отсутствие значения и отличается от undefined-переменной тем, что значение, находящееся в ней, было ей явным образом назначено. Для того чтобы проверить значение на null, достаточно воспользоваться оператором строгого равенства. Для проверки на null, как и в случае с проверкой на undefined, не следует пользоваться оператором нестрогого равенства, считающим равными значения null и undefined.
var foo = null;
console.log(foo === null); // true
console.log(typeof foo === 'object'); // true
console.log(foo == undefined); // true Не используйте такую конструкцию для проверки на null!
Я стараюсь никогда не оставлять переменные в необъявленном состоянии, или в состоянии, когда они объявлены, но им явным образом не назначено никакого значения. Если я не собираюсь записывать в переменную какое-то значение сразу после её объявления, я записываю в неё null. Если вы пользуетесь линтером, то он обычно сообщает о случаях использования необъявленных переменных.
→ Источник [5]
Шаблон «Открытый модуль» (Revealing Module) является разновидностью шаблона «Модуль» (Module). Цель использования этого шаблона заключается в поддержке инкапсуляции и в открытии некоторых свойств и методов, возвращённых в объектном литерале. Вот как будет выглядеть непосредственная реализация этого шаблона:
var Exposer = (function() {
var privateVariable = 10;
var privateMethod = function() {
console.log('Inside a private method!');
privateVariable++;
}
var methodToExpose = function() {
console.log('This is a method I want to expose!');
}
var otherMethodIWantToExpose = function() {
privateMethod();
}
return {
first: methodToExpose,
second: otherMethodIWantToExpose
};
})();
Exposer.first(); // Вывод: This is a method I want to expose!
Exposer.second(); // Вывод: Inside a private method!
Exposer.methodToExpose; // undefined
Очевидный недостаток этого шаблона заключается в том, что при его использовании нельзя обращаться к приватным методам.
→ Источник [13]
Эти объекты ведут себя по-разному в том случае, если переменная, содержащая ссылку на объект, являющийся ключом одной из пар ключ/значение, оказывается недоступной. Вот пример:
var map = new Map();
var weakmap = new WeakMap();
(function() {
var a = {
x: 12
};
var b = {
y: 12
};
map.set(a, 1);
weakmap.set(b, 2);
})()
После того, как завершается выполнение IIFE, у нас уже не будет доступа к объектам a и b. Поэтому сборщик мусора удаляет ключ b из weakmap и очищает память. А вот содержимое map остаётся при этом неизменным.
В результате оказывается, что объекты WeakMap позволяют сборщику мусора избавляться от тех своих записей, на ключи которых нет ссылок во внешних переменных. Объекты map хранят пары ключ/значение вне зависимости от наличия или отсутствия внешних ссылок на ключи. То же самое можно сказать и о реализации структуры данных Map с использованием обычных массивов. В WeakMap используются «слабые» ссылки на ключи. Они не препятствуют работе сборщика мусора в том случае, если на объект, используемый в роли ключа, нет других ссылок.
→ Источник [14]
Параметры всегда передаются по значению, но в переменные, представляющие объекты, записаны ссылки на объекты. Поэтому, когда в функцию передают объект и меняют свойство этого объекта, это изменение сохраняется в объекте и при выходе из функции. В результате возникает ощущение того, что параметры в функции передаются по ссылке. Но если изменить значение переменной, представляющей объект, это изменение не повлияет на объекты, находящиеся за пределами функции.
Вот пример:
function changeStuff(a, b, c)
{
a = a * 10;
b.item = "changed";
c = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);
console.log(obj2.item);
Вот что выведет этот код:
10
changed
unchanged
→ Источник [15]
Для того чтобы обеспечить «глубокую заморозку» объекта с использованием Object.freeze(), нужно создать рекурсивную функцию, которая «замораживает» свойства объекта, которые также являются объектами.
Вот пример обычной «заморозки» объекта:
let person = {
name: "Leonardo",
profession: {
name: "developer"
}
};
Object.freeze(person); // делает объект иммутабельным
person.profession.name = "doctor";
console.log(person); //вывод { name: 'Leonardo', profession: { name: 'doctor' } }
Вот — «глубокая заморозка»:
function deepFreeze(object) {
let propNames = Object.getOwnPropertyNames(object);
for (let name of propNames) {
let value = object[name];
object[name] = value && typeof value === "object" ?
deepFreeze(value) : value;
}
return Object.freeze(object);
}
let person = {
name: "Leonardo",
profession: {
name: "developer"
}
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object
Сообщение об ошибке выводится лишь в строгом режиме. В обычном режиме значение не меняется без вывода сообщений об ошибках.
→ Источник [16]
Самое важное, что нужно понять о this, заключается в том, что у функций нет фиксированного значения this. Это значение зависит от того, как именно вызывается функция. Если мы говорим о том, что функция вызывается с некоторым конкретным значением this, это значит, что это значение определяется не во время объявления функции, а во время её вызова. Вот некоторые особенности this:
someFunc()), то this будет ссылаться на глобальный объект (в браузере это window). Если код выполняется в строгом режиме, то в this будет записано значение undefined.this будет представлено объектом, которому принадлежит метод.this будет представлено тем, что указано в качестве первого аргумента call или apply.this будет целевой элемент события.new, то в this будет новый объект, прототип которого установлен в качестве свойства prototype функции-конструктора.this функции будет жёстко привязано к значению, переданному bind в качестве первого аргумента. Это — единственное исключение из правила, в соответствии с которым функции не имеют жёстко заданного значения this. Функции, созданные с использованием bind, имеют иммутабельное значение this.→ Источник [20]
.next() каждый вызов этого метода приводит к возврату одного значения с помощью ключевого слова yield. При использовании конструкции async/await await-выражения выполняются последовательно.{value: X, done: Boolean}, а асинхронные функции возвращают промисы, разрешаемые со значением X, либо завершаются с ошибкой.Вот асинхронная функция:
// Асинхронная функция
async function init() {
const res1 = await doTask1();
console.log(res1);
const res2 = await doTask2(res1);
console.log(res2);
const res3 = await doTask3(res2);
console.log(res3);
return res3;
}
init();
Вот аналогичный генератор.
// Эта функция выполняет генератор
function runner(genFn) {
const itr = genFn();
function run(arg) {
let result = itr.next(arg);
if (result.done) {
return result.value;
} else {
return Promise.resolve(result.value).then(run);
}
}
return run;
}
// Вызывает функцию runner с передачей ей генератора
runner(function* () {
const res1 = await doTask1();
console.log(res1);
const res2 = await doTask2(res1);
console.log(res2);
const res3 = await doTask3(res2);
console.log(res3);
return res3;
});
→ Источник [21]
Уважаемые читатели! Какие вопросы по JavaScript встречались вам на собеседованиях?
Автор: ru_vds
Источник [23]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/352464
Ссылки в тексте:
[1] Image: https://habr.com/ru/company/ruvds/blog/499014/
[2] Источник: https://www.fullstack.cafe/
[3] Источник: https://stackoverflow.com/questions/8228281/what-is-the-function-construct-in-javascript
[4] Источник: https://stackoverflow.com/questions/22939130/when-should-i-use-arrow-functions-in-ecmascript-6
[5] Источник: https://github.com/yangshun/front-end-interview-handbook/blob/master/questions/javascript-questions.md
[6] Источник: https://stackoverflow.com/questions/33124058/object-freeze-vs-const
[7] Источник: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
[8] Источник: https://stackoverflow.com/questions/20768922/javascript-generators-understanding-them
[9] Источник: https://github.com/kennymkchan/interview-questions-in-javascript
[10] Источник: https://github.com/ganqqwerty/123-Essential-JavaScript-Interview-Question/blob/master/README.md
[11] Источник: http://www.dofactory.com/javascript/prototype-design-pattern
[12] Источник: https://github.com/ajzawawi/js-interview-prep/blob/master/answers/es6/temporal-dead-zone.md
[13] Источник: https://scotch.io/bar-talk/4-javascript-design-patterns-you-should-know
[14] Источник: https://stackoverflow.com/questions/15604168/whats-the-difference-between-es6-map-and-weakmap
[15] Источник: https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language
[16] Источник: https://medium.com/@leonardobrunolima/javascript-tips-object-freeze-vs-const-4cc0fd30449a
[17] call: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/call
[18] apply: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
[19] bind: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
[20] Источник: https://stackoverflow.com/questions/80084/in-javascript-why-is-the-this-operator-inconsistent
[21] Источник: https://codeburst.io/javascript-generator-yield-next-async-await-e428b0cb52e4
[22] Image: https://ruvds.com/ru-rub/#order
[23] Источник: https://habr.com/ru/post/499014/?utm_source=habrahabr&utm_medium=rss&utm_campaign=499014
Нажмите здесь для печати.