Но так сложилось, что я снова вышла на тропу
Этот виток собеседований ознаменовался большим обилием алгоритмических задач. Сложно сказать тенденция ли это, или это мне так повезло.
Когда вся страна уже вышла на работу, а программисты, как избранные, продолжают работать из дома, еще не поздно найти какую-нибудь вторую работу.
Задача: Написать полифилл для Promise.
Целесообразность такого вопроса вызывает вопросы, даже если нужно поддерживать пресловутый IE 11 (процент использования которого на момент написания статьи колеблется где-то в районе 0.5% — 1.5% по разным статистическим данным), то есть куча готовых полифиллов.
Однако, он, вероятно, призван показать знание того, как в принципе работает Promise, что из себя представляет и как с ним работать. Ну что же, можно и с этой стороны зайти, конечно, но можно было бы и просто спросить «как работать с Promise».
Еще пару раз просто спрашивали теоретически, как бы я реализовала.
function Promise(fn) {
this.thenHandlers = [];
this.catchHandlers = [];
this.isResolved = false;
this.isRejected = false;
setTimeout(() => fn(this.applyResolve.bind(this), this.applyReject.bind(this)));
}
Promise.prototype = {
applyResolve: function () {
this.thenHandlers.forEach((handler) => handler());
this.isResolved = true;
},
applyReject: function () {
this.catchHandlers.forEach((handler) => handler());
this.isRejected = true;
},
then: function (handler) {
if (this.isResolved) {
handler();
} else {
this.thenHandlers.push(handler);
}
return this;
},
catch: function (handler) {
if (this.isRejected) {
handler();
} else {
this.catchHandlers.push(handler);
}
return this;
}
};
const p = new Promise((resolve, reject) => (
Math.round(Math.random() * 10) % 2 === 0
? resolve()
: reject()
));
p
.then(function () {
console.log('resolved');
})
.catch(function () {
console.log('rejected');
});
Задача: Реализовать аналог Promise.all.
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let resolvedCount = 0;
promises.forEach((promise, index) => {
promise
.then((result) => {
results[index] = result;
resolvedCount++;
if (resolvedCount === promises.length) {
resolve(results);
}
})
.catch((err) => reject(err));
});
});
}
promiseAll([
new Promise((resolve) => {
setTimeout(() => resolve('foo'), 5000)
}),
new Promise((resolve, reject) => {
setTimeout(() => resolve('bar'), 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
Math.round(Math.random() * 10) % 2 === 0
? resolve('baz')
: reject(new Error());
}, 300);
}),
])
.then((res) => console.log('RESOLVED: ', res))
.catch((err) => console.log('REJECTED: ', err));
Задача: Реализовать аналог Function.prototype.bind.
С появлением Rest parameters реализация этой задачи стала чуть проще, чем прежде, когда приходилось делать arguments.slice.
Function.prototype.bind = function(context, ...argsBind) {
const fn = this;
return function (...args) {
return fn.apply(context, argsBind.concat(args))
};
};
Вопрос: Что такое делегирование событий? Плюсы/минусы/подводные камни.
ициатора события, узнать который можно из свойства объекта события event.target.
Такой подход возможен благодаря особенностям событийной модели DOM-дерева, а конкретно такой особенности, как всплытие событий.
Плюсы
Такой подход будет хорош, если элементы на странице или в какой-то области создаются динамически на протяжении какого-то промежутка времени. В этом случае, при использовании делегирования, мы можем позволить себе добавить обработчик события единожды и не отслеживать тот момент когда эти элементы будут созданы.
Кроме прочего это в лучшую сторону отразится на потреблении памяти, если таких элементов будет немало.
На самом деле, подход с делегированием событием имеет право на жизнь даже если все элементы уже созданы в DOM и их состав не предполагает динамического изменения, но при этом их количество достаточно велико.
Минусы
Чуть меньшая «прозрачность» и очевидность по сравнению с тем когда обработчик события добавляется непосредственно для нужного элемента.
Потенциально чуть больше нагрузки ложится на CPU, т.к. любое возникновения события внутри родительского элемента запускает обработчик и поиск необходимого элемента для проверки необходимости работы основного обработчика. Так что тут всё индивидуально, в каждом конкретном случае надо смотреть насколько вероятны возникновения событий, которые будут приходиться «мимо» целевых элементов и что нам более важно оптимизировать — память или CPU.
Подводные камни
Любой неосторожный event.stopPropagation может прервать цепочку всплытия события и оно не дойдёт до родительского элемента, на котором установлен обработчик.
Если реализуете делегирование собственноручно, а не используете готовое решение, то не стоит забывать про тот нюанс, что недостаточно проверить event.target на соответствие требуемому элементу. Необходимо искать по дереву вверх до тех пор пока либо не встретим либо этот элемент, либо родительский, либо дойдём до вершины дерева. Причиной тому служит всё то же всплытие событий — инициатором события может оказаться один из элементов, вложенных в наш, а не он сам.
Задача: Логическое продолжение предыдущего вопроса — реализовать делегирование.
<div class="wrapper">
<div class="child"><div><div><div>click me</div></div></div></div>
<div class="child"><div><div><div>click me</div></div></div></div>
<div class="child"><div><div><div>click me</div></div></div></div>
<div class="other"><div><div><div>dont't click me</div></div></div></div>
</div>
const delegate = (eventName, el, selector, handler) => {
el.addEventListener(eventName, (event) => {
let node = event.target;
const items = [].slice.call(el.querySelectorAll(selector));
if (items.length) {
while (node !== el && node !== null) {
const isTarget = items.some(item => node === item);
if (isTarget) {
handler(node);
break;
} else {
node = node.parentNode;
}
}
}
});
};
delegate('click', document.querySelector('.wrapper'), '.child', (el) => el.style.backgroundColor = 'blue');
Задача: Уникализация значений в массиве.
Необходимо написать функцию, принимающую в аргументах массив целых чисел и возвращающую новый массив, состоящий только из уникальных значений первого массива.
Например:
unique([1, 1, 2, 2, 4, 2, 3, 7, 3]); // => [1, 2, 4, 3, 7]
function unique(arr) {
const res = [];
arr.forEach((item) => {
if (res.indexOf(item) === -1) {
res.push(item);
}
});
return res;
}
function unique(arr) {
const res = {};
arr.forEach((item) => {
res[item] = '';
});
return Object.keys(res).map(item => Number(item));
}
function unique(arr) {
return arr.filter((item, index, self) => (self.indexOf(item) === index));
}
Задача: «Расплющивание» массива.
Необходимо написать функцию, принимающую в аргументах многомерный массив неограниченной вложенности и возвращающую новый одномерный массив, состоящий из элементов со всех уровней вложенности исходного массива.
Например:
flat([1, [2, [3, [4,5]]]]); // => [1, 2, 3, 4, 5]
function flat(arr) {
let res = [];
arr.forEach((item) => {
if (Array.isArray(item)) {
res = res.concat(flat(item));
} else {
res.push(item);
}
});
return res;
}
Я считаю, что именно с него надо начать ответ на этот вопрос, и только когда (именно когда, а не если) интервьюер скажет, что такое решение ему не подходит и нужно всё сделать руками, приниматься за вышеупомянутое решение через рекурсию.
[1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]].flat(Infinity);
Задача: Написать функцию, принимающую аргументом массив чисел и возвращающую новый массив, состоящий из удвоенных значений первого.
Например:
f([1, 2, null, 7, 8, null, 3]); // => [2, 4, 14, 16, 6]
function f(arr) {
return arr
.filter(item => item !== null)
.map(item => item * 2);
}
Задача: Обход дерева
Дана структура данных в виде дерева:
const tree = {
value: 1,
children: [
{
value: 2,
children: [
{ value: 4 },
{ value: 5 },
]
},
{
value: 3,
children: [
{ value: 6 },
{ value: 7 },
]
}
]
};
Необходимо написать функцию, возвращающую значения всех вершин дерева:
getTreeValues(tree); // => [1, 2, 3, 4, 5, 6, 7]
function getTreeValues(tree) {
let values = [ tree.value ];
if (Array.isArray(tree.children)) {
tree.children.forEach(item => values = values.concat(getTreeValues(item)));
}
return values;
}
Через цикл:
function getTreeValues(tree) {
const tmpTree = [tree];
const res = [];
let current;
while (tmpTree.length > 0) {
current = tmpTree.shift();
res.push(current.value);
if (current.children) {
current.children.forEach(item => tmpTree.push(item));
}
}
return res
}
Задача: Сумма вершин дерева
Очень похоже на предыдущую задачу, только требуется найти сумму значений.
function getTreeSum(node) {
let sum = node.value;
if (Array.isArray(node.children)) {
node.children.forEach(item => sum += getTreeSum(item));
}
return sum;
}
Как видно не только задача, но и её решение практически идентичны предыдущей задаче.
Также, как и предыдущую её можно решить без рекурсии, обойдясь циклом, приводить его не буду, т.к. оно тоже практически идентично предыдущей задаче.
Задача: Сортировка нечётных.
Необходимо написать функцию, принимающую в аргументах массив и возвращающую новый массив, в котором отсортированы все нечетные числа по возрастанию, в то время как чётные остаются на своих местах.
Например:
oddSort([7, 3, 4, 9, 5, 2, 17]); // => [3, 5, 4, 7, 9, 2, 17]
function oddSort(arr) {
arr.forEach((item, index) => {
if (item % 2 === 1) {
let sortNumber = item;
for (let i = 0; i < index; i++) {
if (arr[i] % 2 === 1) {
if (arr[i] > sortNumber) {
const tmp = sortNumber;
sortNumber = arr[i];
arr[i] = tmp;
}
}
}
arr[index] = sortNumber;
}
});
return arr;
}
Задача: Идентичный алфавит
Необходимо написать функцию, принимающую в аргументах две строки и возвращающую true, если эти строки состоят из идентичных букв и false в противном случае.
Например:
isEqualSymbols('кот', 'ток'); // => true
isEqualSymbols('кот', 'тик'); // => false
Я уже писала про эту задачу в предыдущей части, но есть вариант решения получше.
function isEqualSymbols(str1, str2) {
if (str1.length !== str2.length) {
return false;
}
if (str1.split('').sort().join('') === str2.split('').sort().join('')) {
return true;
}
return false;
}
Этот вариант решения исходит из того, что каждый символ уникален сам по себе и если в одной из строк встречаются повторяющиеся символы, а в другой нет, то это разные наборы символов и в результате должно вернуться false.
Если же будет необходимо исходить из того, что символы не уникальны сами по себе и повторные символы должны игнорироваться, то можно предварительно уникализировать символы в обоих строках, воспользовавшись одним из решений из задачи об уникализации значений в массиве.
Задача: Бомба
Надо реализовать «бомбу» (в виде функции-конструктора), которая получает на входе время, через которое взорвется и некоторый «звук взрыва» (строку, которую вернет через заданное время). С фантазией задача.
function Bomb(message, delay) {
this.message = message;
setTimeout(this.blowUp.bind(this), delay * 1000); // взрываем через delay sec
}
Bomb.prototype.blowUp = function () {
console.log(this.message);
};
new Bomb("Explosion!", .5);
Задача: «Сжатие строк»
Необходимо реализовать функцию, принимающую в аргументах строку, состоящую из букв и вернуть новую строку, в которой повторяющиеся буквы заменены количеством повторений.
Например:
rle('AVVVBBBVVXDHJFFFFDDDDDDHAAAAJJJDDSLSSSDDDD'); // => 'AV3B3V2XDHJF4D6HA4J3D2SLS3D4'
function rle(str) {
const result = [str[0]];
let count = 1;
for (let i = 1; i < str.length; i++) {
if (str[i] === str[i - 1]) {
count++;
if (i === str.length - 1) {
result.push(str[i]);
if (count > 1) {
result.push(count);
}
}
} else {
if (i > 1) {
result.push(str[i - 1]);
}
if (i === str.length - 1) {
result.push(str[i]);
}
if (count > 1) {
result.push(count);
}
count = 1;
}
}
return result.join('');
}
Вопрос: Что получится в результате выполнения кода и почему?
var obj = {};
function func(x) {
x = 1;
return x;
}
func(obj); // => ?
console.log(obj); // => ?
Несмотря на то, что объекты в JavaScript передаются в параметры функций по ссылке, obj не изменится.
Внутри функции создаётся локальная переменная x, в которую изначально попадет ссылка на obj, но позже эта переменная переписывается на числовое значение 1. Т.е. меняется само значение переменной x, но меняется значение, которое находится по ссылке, переданной изначально в функцию.
Вопрос: Как передать изображение размером 10Mb с помощью GET-запроса?
P.S.: Вопрос из разряда тех, на которые нет правильного ответа, потому как единственно верным ответом на этот вопрос был бы — не делайте так, не отправляйте файлы методом GET, даже не думайте об этом и всё будет хорошо.
И это не тот случай когда хочется услышать размышления, а дойдёт ли собеседуемый до ожидаемого ответа или нет уже не так важно. Потому как в этом случае можно было бы поставить задачу иначе — указать некоторые дополнительные требования к загрузке файлов: например, чт файлы могут быть неприлично большого размера и необходимо реализовать загрузку с возможностью догрузки, если в каком-то временном промежутке у клиента будут проблемы с сетью. Я имею ввиду, что если хочется услышать про возможность нарезки файлов на клиенте, то можно придумать условия и поадекватнее.
Вопрос: Назовите известные вам HTTP-методы. Что такое CRUD?
GET — read — используется только для получения данных.
POST — create — создание новых сущностей.
PUT/PATCH — update — обновление данных.
DELETE — delete — удаление.
Вообще HTTP-методов сильно больше, помимо выше перечисленных есть OPTIONS, HEAD, TRACE и др.
Есть ещё пул вопросов, связанных с фреймворками и смежными с frontend'ом темами, возможно оформлю их в отдельную статью.
oxidmod
developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Set
anonymous
Fess_blaga
На собеседовании была такая задача, про Set сразу не подумал, решил проходом по массиву — сказали что правильно, но через Set лучше и элегантнее.
Через неделю другое собеседование, задача та же, делаю через Set — сказали что правильно, но они хотят посмотреть на знание перебирающих методов массива и попросили сделать по другому)