Irbisa · Шпаргалка

Продвинутый JavaScript

Приведение типов, call, apply и bind, оператор new, Map, Set и WeakMap, итераторы и генераторы, Proxy, сборка мусора, задачи «что выведет», свои Promise.all, curry и EventEmitter

Middle-разработчик · 18 вопросов

  1. 01

    В чём разница между Map и обычным объектом и между Set и массивом?

    Map принимает ключи любого типа, хранит порядок вставки, знает свой size и итерируется; обычный объект превращает каждый ключ в строку или символ и подходит для записей с известными полями.

    const user = { id: 7 }; const plain = {};plain[user] = 'Ada'; // the key becomes the string "[object Object]"plain[2] = 'two';plain.b = 'b';…
  2. 02

    Что такое WeakMap, WeakSet и WeakRef и когда их использовать вместо Map и Set?

    WeakMap и WeakSet держат ключи слабо: запись не удерживает объект-ключ в памяти и исчезает, когда этот объект собран сборщиком мусора.

    // derived data per state object: entries go away together with old statesconst totals = new WeakMap(); function getTotal(cart) {  if (!totals.has(cart)) {    console.log('computing');…
  3. 03

    Чему равно каждое из этих выражений и какие правила приведения типов это объясняют?

    console.log([] + {});console.log('5' - 2);console.log('5' + 2);console.log(true + 1);console.log(null + 1);console.log(undefined + 1);console.log([] == ![]);console.log('b' + 'a' + +'a' + 'a');

    Выведется [object Object], 3, 52, 2, 1, NaN, true и baNaNa.

  4. 04

    Что делают call, apply и bind и как написать полифил для Function.prototype.bind?

    Все три задают this для обычной функции.

    Function.prototype.myBind = function (thisArg, ...preset) {  const fn = this;  return function bound(...args) {    if (new.target) return new fn(...preset, ...args); // new ignores the bound this    return fn.apply(thisArg, [...preset, ...args]);  };…
  5. 05

    Что выведет этот код и почему this меняется от вызова к вызову?

    class Counter {  count = 0;  inc() {    return ++this.count;  }  incArrow = () => ++this.count;} const c = new Counter();const other = { count: 10, inc: c.inc, incArrow: c.incArrow };console.log(c.inc());console.log(other.inc());console.log(other.incArrow());console.log(c.inc.bind(other).call(c));const { inc } = c;inc();

    Код выведет 1, 11, 2 и 12, а затем бросит TypeError: Cannot read properties of undefined (reading 'count').

  6. 06

    Что по шагам делает оператор new и как реализовать его заново обычной функцией?

    new Fn(args) создаёт пустой объект с прототипом Fn.prototype, вызывает Fn с this, равным этому объекту, и возвращает объект, если только Fn сама не вернула объект: тогда результатом будет он.

    function myNew(Constructor, ...args) {  const obj = Object.create(Constructor.prototype); // 1. link the prototype  const result = Constructor.apply(obj, args); // 2. run the body with this = obj  const isObject = result !== null && (typeof result === 'object' || typeof result === 'function');  return isObject ? result : obj; // 3. an object returned by the body wins}…
  7. 07

    Что делают Object.freeze, Object.seal и дескрипторы свойств и почему замороженный объект не является глубоко неизменяемым?

    У каждого свойства есть дескриптор: value и флаг writable (или вместо них геттер и сеттер), а также enumerable и configurable.

    const config = Object.freeze({ api: '/v1', retry: { count: 3 } });config.retry.count = 5; // allowed: freeze is shallowtry {  config.api = '/v2'; // modules and classes are strict, so this throws} catch (error) {  console.log(error.message); // Cannot assign to read only property 'api' of object '#<Object>'…
  8. 08

    Что делает объект итерируемым, как работает Symbol.iterator и чем for...of отличается от for...in?

    Объект итерируемый, если у него есть метод [Symbol.iterator](), который возвращает итератор: объект с методом next(), возвращающим { value, done }.

    class Range {  constructor(from, to) {    this.from = from;    this.to = to;  }…
  9. 09

    Что такое функции-генераторы и где они до сих пор полезны во фронтенде?

    Функция-генератор (function*) не выполняет тело сразу, а возвращает итератор; каждый next() выполняет тело до следующего yield и останавливается там, сохраняя всё локальное состояние.

    function* idGenerator(prefix) {  let n = 1;  while (true) yield `${prefix}-${n++}`; // infinite, but lazy: runs only on next()} const ids = idGenerator('row');…
  10. 10

    Что выведет эта цепочка промисов и какой then или catch обработает каждую ошибку?

    Promise.resolve(1)  .then((x) => {    throw new Error(`boom ${x}`);  })  .then((x) => console.log('a', x))  .catch((err) => err.message.toUpperCase())  .then(    (x) => {      console.log('b', x);      throw new Error('again');    },    (err) => console.log('c', err.message),  )  .finally(() => console.log('d'))  .catch((err) => console.log('e', err.message));

    Цепочка выведет b BOOM 1, затем d, затем e again.

  11. 11

    Что такое каррирование и как реализовать функцию curry, которая поддерживает и sum(1)(2)(3), и sum(1, 2)(3)?

    Каррирование превращает функцию от нескольких аргументов в цепочку вызовов, которая собирает аргументы, пока их не станет достаточно.

    function curry(fn) {  return function curried(...args) {    if (args.length >= fn.length) return fn.apply(this, args);    return (...more) => curried.apply(this, [...args, ...more]);  };}…
  12. 12

    Как написать функцию memoize и как не дать её кэшу расти бесконечно?

    Оберните функцию, постройте ключ из аргументов, верните результат из кэша, если ключ уже есть, а иначе вычислите и сохраните его.

    function memoize(fn, { maxSize = 100, key = (...args) => JSON.stringify(args) } = {}) {  const cache = new Map(); // a Map remembers insertion order: the first key is the oldest   return function (...args) {    const k = key(...args);    if (cache.has(k)) {…
  13. 13

    Как реализовать EventEmitter с методами on, off, emit и once?

    Храните в Map список слушателей для каждого имени события: on добавляет, off удаляет, emit вызывает каждого слушателя с аргументами, а once регистрирует обёртку, которая удаляет себя перед вызовом настоящего слушателя.

    class EventEmitter {  #listeners = new Map(); // event name -> array of listeners   on(event, listener) {    this.#listeners.set(event, [...(this.#listeners.get(event) ?? []), listener]);    return () => this.off(event, listener); // handy as a useEffect cleanup…
  14. 14

    Как реализовать Promise.all с нуля, сохранив порядок результатов и отклоняясь на первой ошибке?

    Верните новый промис, оберните каждый входной элемент в Promise.resolve, сохраняйте каждое значение по его индексу и считайте выполненные: резолвьте, когда счётчик дойдёт до общего числа, и отклоняйте, как только отклонится любой элемент.

    function promiseAll(iterable) {  return new Promise((resolve, reject) => {    const items = Array.from(iterable);    const results = new Array(items.length);    let pending = items.length;    if (pending === 0) return resolve(results);…
  15. 15

    Как написать retry(fn, attempts), которая повторяет упавший асинхронный вызов с экспоненциальной задержкой?

    Вызывайте fn в цикле внутри try/catch; при ошибке, которую стоит повторить, ждите baseDelay * 2 ** attempt миллисекунд со случайным разбросом (jitter) и пробуйте снова, а после последней попытки пробросьте ошибку дальше.

    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function retry(fn, { attempts = 3, baseDelay = 200, maxDelay = 5000, shouldRetry = () => true } = {}) {  for (let attempt = 1; ; attempt++) {    try {      return await fn(attempt); // without await, a rejection would skip this catch…
  16. 16

    Как выполнить список асинхронных задач так, чтобы одновременно выполнялось не больше N (пул промисов), и сохранить порядок результатов?

    Запустите N воркеров с общим курсором по списку задач: каждый воркер берёт следующий индекс, ждёт эту задачу, кладёт результат по тому же индексу и берёт следующую, пока список не кончится.

    async function runPool(tasks, limit) {  const results = new Array(tasks.length);  let next = 0; // shared cursor: safe, because next++ runs without an await in between   async function worker() {    while (next < tasks.length) {…
  17. 17

    Что такое Proxy, как его используют Immer, MobX и Vue и чего он стоит?

    Proxy оборачивает объект и пропускает операции над ним (чтение, запись, удаление, in, получение списка ключей) через ваши функции-ловушки (traps), а Reflect выполняет поведение по умолчанию.

    // a tiny Vue-style reactive object: record reads, react to writesfunction reactive(target, onChange) {  return new Proxy(target, {    get(obj, key, receiver) {      console.log('read', String(key)); // Vue's track(): remember who depends on key      return Reflect.get(obj, key, receiver);…
  18. 18

    Как работает сборка мусора в движке JavaScript вроде V8 и как замыкание может случайно удержать в памяти большой объект?

    Движок освобождает объекты, до которых больше нельзя добраться от корней (глобальные объекты, стек вызовов, живые хэндлы), а не объекты, которые просто не используются.

    // run with: node --expose-gc gc.jsconst handlers = []; function setup() {  const report = new Array(1_000_000).fill(0); // about 8 MB  const total = () => report.length; // the only closure that reads report…