Почему 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
, браузер сначала выполнит макрозадачу, а затем сначала выполнит микрозадачу, если есть асинхронный код.
Итак, правильная последовательность цикла событий выглядит так
- Выполнение синхронного кода, который является задачей макроса
- Стек выполнения пуст, проверьте, есть ли микрозадачи для выполнения
- Выполнить все микрозадачи
- Рендеринг пользовательского интерфейса, если это необходимо
- Затем запустите следующий раунд цикла событий и выполните асинхронный код в задаче макроса.
Из приведенной выше последовательности циклов событий мы видим, что если асинхронный код в макрозадаче требует большого количества вычислений и должен манипулировать 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
Этап опроса очень важен, на этом этапе система выполняет две функции.
- Исполненный таймер
- Выполнение событий в очереди опроса
И когда в опросе нет таймера, будут найдены следующие две вещи
- Если очередь опроса не пуста, она будет проходить через очередь обратного вызова и выполняться синхронно до тех пор, пока очередь не станет пустой или не будет достигнуто системное ограничение.
- Если очередь опроса пуста, происходят две вещи
- Если есть
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
Наконец, прикрепите мой общедоступный номер