Полифиллы: bind, Promise.all, debounce, deepClone
↑ 2.1 JavaScript: продвинутый · ← Предыдущая
Зачем это на собесе
Реализация встроенных функций — частое задание live coding. Проверяет знание
this, замыканий и промисов.
Объяснение
// bind
Function.prototype.myBind = function (ctx, ...bound) {
const fn = this;
return function bound_(...args) {
return fn.apply(this instanceof bound_ ? this : ctx, [...bound, ...args]); // поддержка new
};
};
// Promise.all
function promiseAll(items) {
return new Promise((resolve, reject) => {
const results = []; let left = 0, i = 0;
for (const item of items) {
const idx = i++; left++;
Promise.resolve(item).then(v => { results[idx] = v; if (--left === 0) resolve(results); }, reject);
}
if (left === 0) resolve(results);
});
}
// deepClone (циклы, Date, Map, Set)
function deepClone(x, seen = new WeakMap()) {
if (x === null || typeof x !== "object") return x;
if (seen.has(x)) return seen.get(x);
if (x instanceof Date) return new Date(x);
if (x instanceof Map) { const m = new Map(); seen.set(x, m); x.forEach((v, k) => m.set(deepClone(k, seen), deepClone(v, seen))); return m; }
if (x instanceof Set) { const s = new Set(); seen.set(x, s); x.forEach(v => s.add(deepClone(v, seen))); return s; }
const out = Array.isArray(x) ? [] : Object.create(Object.getPrototypeOf(x));
seen.set(x, out);
for (const k of Reflect.ownKeys(x)) out[k] = deepClone(x[k], seen);
return out;
}
// once, memoize, flatten, curry, EventEmitter
const once = (fn) => { let done = false, res; return (...a) => done ? res : (done = true, res = fn(...a)); };
const flat = (arr, d = Infinity) => arr.reduce((acc, x) => Array.isArray(x) && d > 0 ? acc.concat(flat(x, d - 1)) : acc.concat(x), []);
class Emitter {
#h = new Map();
on(e, f) { (this.#h.get(e) ?? this.#h.set(e, new Set()).get(e)).add(f); return () => this.off(e, f); }
off(e, f) { this.#h.get(e)?.delete(f); }
emit(e, ...a) { this.#h.get(e)?.forEach(f => f(...a)); }
}
// Promise-пул с ограничением параллелизма
async function pool(tasks, limit) {
const results = []; let i = 0;
const worker = async () => { while (i < tasks.length) { const idx = i++; results[idx] = await tasks[idx](); } };
await Promise.all(Array.from({ length: limit }, worker));
return results;
}(Реализации debounce и throttle — в Debounce и throttle.)
Как реализовывать на интервью: уточнить контракт (поведение на ошибках, порядок результатов, this), написать базовый вариант, затем обработать крайние случаи и назвать сложность.
Нюансы и подводные камни
Promise.allсохраняет порядок результатов, а не порядок завершения.deepCloneдолжен обрабатывать циклические ссылки,Date,RegExp,Map/Set.bind: результат должен поддерживать вызов черезnew.- Не забывайте про пустой ввод и нестандартные итерируемые.
Практика
- Реализуйте
myCall,myApply,myBind,promiseAny,promiseRace. - Реализуйте
LRUCache,EventEmitter,poolи напишите тесты. - Сравните свой
deepCloneсstructuredClone.
Вопросы с ответами
Как реализовать
bind?Вернуть функцию, вызывающую оригинал через
applyс сохранённым контекстом и предустановленными аргументами.
Как обработать циклические ссылки при копировании?
Хранить уже скопированные объекты в
WeakMapи возвращать копию при повторной встрече.