Некоторое время назад возникла необходимость экспортировать Excel из внешнего интерфейса. Вообще говоря, для функции экспорта большого объема данных лучше всего оставить это на бэкенде, но бэкенд-брат этого делать не хочет(Слезоточивость), может быть только самостоятельным.
Внешний экспорт самого excel уже имеет очень зрелую библиотеку, такую какjs-xlsx, js-export-excel, так что это не сложно реализовать. Однако, когда экспортированные данные достигают десятков тысяч, будет обнаружено, что страница явно зависла. Причина также очень проста: как правило, мы генерируем Excel на основе данных JSON, возвращаемых серверной частью, но данные, возвращаемые серверной частью, не могут быть напрямую использованы для генерации данных.Нам также необходимо выполнить некоторое форматирование:
const list = await request('/api/getExcelData');
const format = list.map((item) => {
// 对返回的json数据进行格式化
item.time = moment(item.time).format('YYYY-MM-DD HH:mm');
// ... 省略其他各种操作
});
// 根据json生成excel
const toExcel = new ExportJsonExcel(format).saveExcel();
Катон возникает при обработке большого количества данныхmap
работать. Поскольку JS является однопоточным, он монополизирует основной поток при выполнении большого количества сложных операций, что приводит к тому, что другие события на странице не реагируют вовремя, что приводит к феномену приостановленных страниц.
Итак, можем ли мы поместить сложные циклические операции только в поток? пожалуйста, выходиweb workerохватывать
Web Worker
Сначала посмотрите на простой пример
<button id="btn1">js</button>
<button id="btn2">worker</button>
<input type="text">
index.js
const btn1 = document.getElementById('btn1');
btn1.addEventListener('click', function () {
let total = 1;
for (let i = 0; i < 5000000000; i++) {
total += i;
}
console.log(total);
})
Когда вы нажмете btn1, js выполнит множество вычислений, вы обнаружите, что страница зависла, и нажатие ввода не будет иметь никакого ответа.
Мы используем веб-воркеры для оптимизации кода:
worker.js
onmessage = function(e) {
if (e.data === 'total') {
let total = 1;
for (let i = 0; i < 5000000000; i++) {
total += i;
}
postMessage(total);
}
}
index.js
if (window.Worker) {
const myWorker = new Worker('worker.js');
myWorker.onmessage = function (e) {
console.log('total', e.data);
};
const btn1 = document.getElementById('btn1');
const btn2 = document.getElementById('btn2');
btn1.addEventListener('click', function () {
let total = 1;
for (let i = 0; i < 5000000000; i++) {
total += i;
}
console.log('total', total);
})
btn2.addEventListener('click', function () {
myWorker.postMessage('total');
});
}
При нажатии btn2 страница не застрянет, можно будет нормально вводить ввод
Мы запускаем отдельный рабочий поток для выполнения сложных операций,postMessage
а такжеonmessage
для связи между двумя потоками.
Оптимизация экспорта таблиц Excel.
Глядя на предыдущий пример, мы можем аналогичным образом использовать веб-воркеры для выполнения сложных операций с картами.
worker.js
onmessage = function(e) {
const format = e.data.map((item) => {
// 对返回的json数据进行格式化
item.time = moment(item.time).format('YYYY-MM-DD HH:mm');
// ... 省略其他各种操作
});
postMessage(format);
}
const myWorker = new Worker('worker.js');
myWorker.onmessage = function (e) {
// 根据json生成excel
const toExcel = new ExportJsonExcel(e.data).saveExcel();
};
const list = await request('/api/getExcelData');
myWorker.postMessage(list);
Конечно, для реальных проектов мы обычно используем webpack для их упаковки.В настоящее время необходимо выполнить некоторую специальную обработку, и нам нужно использоватьworker-loader, вы можете обратиться к"Как использовать Web Worker в ES6+Webpack"изучение статьи.
дальнейшая оптимизация
В приведенной выше модификации кода мы просто оптимизировали операцию сопоставления в бизнес-логике. Поскольку библиотека js, которую я использую,js-export-excel
, из егоисходный кодКак видите, для данных, которые мы передаем, он также снова запустит цикл forEach для выполнения двоичного преобразования данных. Таким образом, цикл forEach на этом шаге теоретически может работать и в веб-воркере.
Самый простой способ, который приходит на ум:
worker.js
onmessage = function(e) {
const format = e.data.map((item) => {
// 对返回的json数据进行格式化
item.time = moment(item.time).format('YYYY-MM-DD HH:mm');
// ... 省略其他各种操作
});
// 直接在worker里面生成excel
const toExcel = new ExportJsonExcel(format).saveExcel();
}
прямо вworker.js
Он генерирует Excel внутри. Однако,saveExcel
Этот метод требуетdocument
объект, но в рабочем мы не можем получить доступ к чему-то вродеwindow
document
глобальный объект.
Поэтому изменять можно только исходный код. . .
действительно используетсяdocument
объектисходный кодЭто предложение:
// saveAs和Blob用到了document
saveAs(
new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}),
_options.fileName + ".xlsx"
);
saveExcel
Метод просто нужно изменить на:
// 不生成excel,只返回数据
return s2ab(wbout);
worker.js
onmessage = function(e) {
const format = e.data.map((item) => {
// 对返回的json数据进行格式化
item.time = moment(item.time).format('YYYY-MM-DD HH:mm');
// ... 省略其他各种操作
});
// saveExcel只返回blob数据
const blob = new ExportJsonExcel(format).saveExcel();
postMessage(blob);
}
index.js
myWorker.onmessage = function (e) {
// 在主线程生成excel
saveAs(
new Blob([e.data], {
type: "application/octet-stream"
}),
"test.xlsx"
);
};
Принцип таков: мы помещаем только преобразование данных в worker, а окончательная генерация excel по-прежнему выполняется в основном потоке.
На этом оптимизация завершена!
Суммировать
Мы можем поместить некоторые ресурсоемкие операции в рабочий поток (например, загрузку больших файлов), чтобы основной поток мог своевременно реагировать на действия пользователя, не вызывая задержек. Следует отметить, что время выполнения сложных вычислений в воркере не уменьшится, а иногда и увеличится, ведь запуск воркера тоже потребляет определенное количество производительности.