Анализ нескольких продвинутых вопросов фронтенд-интервью

Node.js интервью внешний интерфейс открытый источник JavaScript

Почему 0,1 + 0,2 != 0,3, объясните почему

Потому что JS использует версию двойной точности IEEE 754 (64-разрядную), и любой язык, использующий IEEE 754, имеет эту проблему.

Мы все знаем, что компьютеры представляют десятичные числа в двоичном виде, поэтому0.1Представлено в двоичном виде как

// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)

Итак, как получить этот двоичный файл, мы можем его вычислить

Десятичные числа отличаются от двоичных и целых чисел. При умножении вычисляются только десятичные разряды, целые биты используются в качестве двоичных значений каждого бита, а первый полученный бит является старшим битом. Итак, мы получаем0.1 = 2^-4 * 1.10011(0011),Так0.2Исчисление в основном такое же, как показано выше, только нужно удалить первый шаг умножения, поэтому мы получаем0.2 = 2^-3 * 1.10011(0011).

Вернитесь и поговорим о двойной точности IEEE 754. Среди шестидесяти четырех битов знаковый бит занимает один, целочисленный бит занимает одиннадцать, а остальные пятьдесят два бита являются десятичными разрядами. так как0.1и0.2Все они представляют собой бесконечно зацикленный двоичный код, поэтому в конце десятичного знака вам нужно определить, следует ли выполнять (точно так же, как десятичное округление).

так2^-4 * 1.10011...001После переноса становится2^-4 * 1.10011(0011 * 12次)010. то сложение двух двоичных значений вместе дает2^-2 * 1.0011(0011 * 11次)0100, это десятичное значение равно0.30000000000000004

Давайте поговорим о нативном решении, как показано в следующем коде.

parseFloat((0.1 + 0.2).toFixed(10))

Одновременно инициируются 10 запросов Ajax, все возвращают результаты отображения, допускается не более трех сбоев, а идеи дизайна

Я полагаю, что многие люди задумаются над этим вопросом впервые.Promise.all, но у этой функции есть ограничение, что если она однажды даст сбой, то она вернётся, и реализовать её напрямую таким образом будет немного проблематично, и её нужно обходить. Ниже приведены две идеи реализации

// 以下是不完整代码,着重于思路 非 Promise 写法
let successCount = 0
let errorCount = 0
let datas = []
ajax(url, (res) => {
     if (success) {
         success++
         if (success + errorCount === 10) {
             console.log(datas)
         } else {
             datas.push(res.data)
         }
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失败次数大于3次就应该报错了
             throw Error('失败三次')
         }
     }
})
// Promise 写法
let errorCount = 0
let p = new Promise((resolve, reject) => {
    if (success) {
         resolve(res.data)
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失败次数大于3次就应该报错了
            reject(error)
         } else {
             resolve(error)
         }
     }
})
Promise.all([p]).then(v => {
  console.log(v);
});

Разработайте систему кеширования 1M на основе Localstorage, и вам необходимо реализовать механизм устранения кеша.

Идеи оформления следующие:

  • К каждому хранимому объекту необходимо добавить два свойства: время истечения и время хранения.
  • Используйте атрибут, чтобы сохранить размер пространства, занимаемого в данный момент в системе, и увеличивайте атрибут каждый раз, когда он сохраняется. Когда значение атрибута больше 1M, необходимо отсортировать данные в системе по времени и удалить определенный объем данных, чтобы обеспечить возможность сохранения текущих данных, которые необходимо сохранить.
  • Каждый раз, когда вы извлекаете данные, вам необходимо определить, истек ли срок действия кэшированных данных, и если срок их действия истек, удалить их.

Ниже приведена реализация кода, идея реализована, но могут быть ошибки, но такого рода проблемы дизайна обычно дают идею дизайна и часть кода, и не нужно писать беспроблемный код.

class Store {
  constructor() {
    let store = localStorage.getItem('cache')
    if (!store) {
      store = {
        maxSize: 1024 * 1024,
        size: 0
      }
      this.store = store
    } else {
      this.store = JSON.parse(store)
    }
  }
  set(key, value, expire) {
    this.store[key] = {
      date: Date.now(),
      expire,
      value
    }
    let size = this.sizeOf(JSON.stringify(this.store[key]))
    if (this.store.maxSize < size + this.store.size) {
      console.log('超了-----------');
      var keys = Object.keys(this.store);
      // 时间排序
      keys = keys.sort((a, b) => {
        let item1 = this.store[a], item2 = this.store[b];
        return item2.date - item1.date;
      });
      while (size + this.store.size > this.store.maxSize) {
        let index = keys[keys.length - 1]
        this.store.size -= this.sizeOf(JSON.stringify(this.store[index]))
        delete this.store[index]
      }
    }
    this.store.size += size

    localStorage.setItem('cache', JSON.stringify(this.store))
  }
  get(key) {
    let d = this.store[key]
    if (!d) {
      console.log('找不到该属性');
      return
    }
    if (d.expire > Date.now) {
      console.log('过期删除');
      delete this.store[key]
      localStorage.setItem('cache', JSON.stringify(this.store))
    } else {
      return d.value
    }
  }
  sizeOf(str, charset) {
    var total = 0,
      charCode,
      i,
      len;
    charset = charset ? charset.toLowerCase() : '';
    if (charset === 'utf-16' || charset === 'utf16') {
      for (i = 0, len = str.length; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode <= 0xffff) {
          total += 2;
        } else {
          total += 4;
        }
      }
    } else {
      for (i = 0, len = str.length; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode <= 0x007f) {
          total += 1;
        } else if (charCode <= 0x07ff) {
          total += 2;
        } else if (charCode <= 0xffff) {
          total += 3;
        } else {
          total += 4;
        }
      }
    }
    return total;
  }
}

Подробное описание Цикл событий

Как мы все знаем, JS — это неблокирующий однопоточный язык, потому что изначально JS был создан для взаимодействия с браузером. Если JS — многопоточный язык, могут возникнуть проблемы при обработке DOM в несколько потоков (новые узлы добавляются в одном потоке, а узлы удаляются в другом).Конечно, мы можем ввести блокировки чтения-записи на решить эту проблему.

JS будет генерировать среды выполнения в процессе выполнения, и эти среды выполнения будут последовательно добавляться в стек выполнения. Если встречается асинхронный код, он будет приостановлен и добавлен в очередь Task (есть разные задачи). Как только стек выполнения станет пустым, Event Loop достанет код, который нужно выполнить, из очереди задач и поместит его в стек выполнения для выполнения, то есть, по сути, асинхронное или синхронное поведение в JS.

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

console.log('script end');

Хотя приведенный выше кодsetTimeoutЗадержка равна 0, что по-прежнему асинхронно. Это связано с тем, что стандарт HTML5 предусматривает, что второй параметр этой функции не должен быть меньше 4 миллисекунд, и нехватка будет автоматически увеличиваться. такsetTimeoutвсе еще будетscript endраспечатать потом.

Различные источники задач будут назначены разным очередям задач, а источники задач можно разделить на микрозадачи и макрозадачи. В спецификации ES6 микрозадачи называютсяjobs, макрозадача называетсяtask.

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

new Promise((resolve) => {
    console.log('Promise')
    resolve()
}).then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout

Хотя приведенный выше кодsetTimeoutписать наPromiseраньше, а потомуPromiseэто микрозадачиsetTimeoutЭто макрозадача, поэтому будет указанная выше печать.

Микрозадачи включаютprocess.nextTick,promise,Object.observe,MutationObserver

Макрозадачи включаютscript,setTimeout,setInterval,setImmediate,I/O,UI rendering

Многие неправильно понимают, что микрозадачи выполняются быстрее, чем макрозадачи, что на самом деле неверно. Поскольку задача макроса включаетscript, браузер сначала выполнит макрозадачу, а затем сначала выполнит микрозадачу, если есть асинхронный код.

Итак, правильная последовательность цикла событий выглядит так

  1. Выполнение синхронного кода, который является задачей макроса
  2. Стек выполнения пуст, проверьте, есть ли микрозадачи для выполнения
  3. Выполнить все микрозадачи
  4. Рендеринг пользовательского интерфейса, если это необходимо
  5. Затем запустите следующий раунд цикла событий и выполните асинхронный код в задаче макроса.

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

Цикл событий в узле

Цикл событий в Node не такой, как в браузере.

Цикл событий Node разделен на 6 этапов, которые последовательно выполняются многократно.

┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<──connections───     │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘
timer

Фаза таймеров будет выполнятьсяsetTimeoutиsetInterval

ОдинtimerУказанное время не является точным, но обратный вызов выполняется как можно скорее после достижения времени, возможно, с задержкой, поскольку система выполняет другие транзакции.

Время для нижней границы имеет диапазон:[1, 2147483647], если установленное время не находится в этом диапазоне, оно будет установлено на 1.

I/O

Фаза ввода-вывода будет выполняться в дополнение к событию закрытия, таймерам иsetImmediateПерезвони

idle, prepare

Внутренняя реализация фазы ожидания, подготовки

poll

Этап опроса очень важен, на этом этапе система выполняет две функции.

  1. Исполненный таймер
  2. Выполнение событий в очереди опроса

И когда в опросе нет таймера, будут найдены следующие две вещи

  • Если очередь опроса не пуста, она будет проходить через очередь обратного вызова и выполняться синхронно до тех пор, пока очередь не станет пустой или не будет достигнуто системное ограничение.
  • Если очередь опроса пуста, происходят две вещи
    • Если естьsetImmediateНеобходимо выполнить, фаза опроса остановится и перейдет к фазе проверки для выполненияsetImmediate
    • если нетsetImmediateНеобходимо выполнить, будет ждать добавления обратного вызова в очередь и немедленно выполнить обратный вызов

Если есть другие таймеры, которые необходимо выполнить, он вернется к фазе таймера для выполнения обратного вызова.

check

проверка выполнения фазыsetImmediate

close callbacks

Фаза закрытых обратных вызовов выполняет событие закрытия

А в Node порядок выполнения таймера в некоторых случаях случайный

setTimeout(() => {
    console.log('setTimeout');
}, 0);
setImmediate(() => {
    console.log('setImmediate');
})
// 这里可能会输出 setTimeout,setImmediate
// 可能也会相反的输出,这取决于性能
// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
// 否则会执行 setTimeout

Конечно, в этом случае порядок выполнения тот же

var fs = require('fs')

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0);
    setImmediate(() => {
        console.log('immediate');
    });
});
// 因为 readFile 的回调在 poll 中执行
// 发现有 setImmediate ,所以会立即跳到 check 阶段执行回调
// 再去 timer 阶段执行 setTimeout
// 所以以上输出一定是 setImmediate,setTimeout

Выше описано выполнение макрозадачи, а микрозадача будет выполняться сразу после завершения каждого из вышеперечисленных этапов.

setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')
    })
}, 0)

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')
    })
}, 0)

// 以上代码在浏览器和 node 中打印情况是不同的
// 浏览器中打印 timer1, promise1, timer2, promise2
// node 中打印 timer1, timer2, promise1, promise2

в узлеprocess.nextTickБудет выполняться перед другими микрозадачами.

setTimeout(() => {
  console.log("timer1");

  Promise.resolve().then(function() {
    console.log("promise1");
  });
}, 0);

process.nextTick(() => {
  console.log("nextTick");
});
// nextTick, timer1, promise1

Наконец, прикрепите мой общедоступный номер