在上一单元中,我们介绍了 Web Worker 概览。Web Worker 可以 通过将 JavaScript 从主线程移到 单独的网络工作器线程,这有助于改善网站的互动体验 进行下一次绘制 (INP) 主线程。不过,仅仅提供概览是不够的,在本单元中, 还提供了具体的 Web Worker 用例。
例如,网站需要将 Exif 元数据从 这并不是一个笼统的概念。事实上,Flickr 等网站 用户可通过一种方式查看 Exif 元数据,从而了解有关 例如色深、相机品牌和型号等, 数据。
不过,用于提取图片并将其转换为 ArrayBuffer
的逻辑:
而提取 Exif 元数据的成本可能
在主线程上执行幸运的是,Web Worker 作用域允许完成这项工作,
从主线程中移除然后,使用 Web Worker 的消息传递流水线,
Exif 元数据作为 HTML 字符串传回主线程,并且
显示给用户
没有 Web Worker 时主线程是什么样的
首先,观察一下在不使用 Web Worker。为此,请执行以下步骤:
- 在 Chrome 中打开一个新标签页,然后打开其开发者工具。
- 打开性能面板。
- 前往 https://exif-worker.glitch.me/without-worker.html。
- 在性能面板中,点击 “开发者工具”窗格
- 粘贴此图片链接,或您自己选择的其他包含 Exif 的图片链接 元数据 - 在该字段中点击 Get that JPEG!(获取 JPEG!)按钮。
- 当界面填充了 Exif 元数据后,再次点击 Record(记录)以 停止录制。
![性能分析器,显示图片元数据提取器应用的 activity 完全发生在主线程上。有两个耗时较长的任务:一个运行提取来获取请求的图片并解码,另一个从图片中提取元数据。](https://web.developers.google.cn/static/learn/performance/web-worker-demo/image/fig-1.opt.png?authuser=9&hl=sk)
请注意,除了可能存在的其他线程(例如光栅化处理程序)之外, 等等 - 应用中的一切操作都在主线程中进行。在主页面 会发生以下情况:
- 表单接受输入并分派
fetch
请求以获取初始 部分。 - 图片数据会转换为
ArrayBuffer
。 exif-reader
脚本用于从 图片。- 从元数据抓取过程中构建 HTML 字符串,然后填充 元数据查看器。
接下来,我们对比一下,实现相同行为,但使用的是 员工!
Web Worker 的主线程实现方式
现在,您已经了解了从 JPEG 文件,我们来看看 worker 包括:
- 在 Chrome 中打开另一个标签页,然后打开其开发者工具。
- 打开性能面板。
- 前往 https://exif-worker.glitch.me/with-worker.html。
- 在性能面板中,点击右上角的记录按钮 。
- 将该图片链接粘贴到相应字段中,然后点击获取该图片!按钮。
- 当界面填充了 Exif 元数据后,点击 record 按钮 即可停止录制。
![显示图片元数据提取器应用同时在主线程和 Web 工作器线程上进行的活动的性能分析器。虽然主线程上仍然有耗时较长的任务,但任务时间要短很多,因为图像提取/解码和元数据提取完全在 Web Worker 线程上进行。唯一的主线程工作涉及将数据传入和传出 Web Worker。](https://web.developers.google.cn/static/learn/performance/web-worker-demo/image/fig-2.opt.png?authuser=9&hl=sk)
这就是 Web Worker 的强大功能。你不用在主分支上做任何事情, 除使用 HTML 填充元数据查看器外,其他所有操作都是在 单独的线程。这意味着主线程被释放出来以执行其他工作。
也许这个版本的最大优势在于
不使用 Web Worker,因此 exif-reader
脚本不会在主函数上加载
而是在 Web 工作器线程上执行这意味着
下载、解析和编译 exif-reader
脚本的操作是在
主线程。
现在,深入了解让这一切成为可能的 Web Worker 代码!
Web Worker 代码一览
仅仅看到 Web Worker 带来的差异是不够的, 至少能理解代码是什么样子,这样您就能知道 在 Web Worker 作用域内运行。
<ph type="x-smartling-placeholder">从需要在 Web Worker 运行之前执行的主线程代码开始 输入图片:
// scripts.js
// Register the Exif reader web worker:
const exifWorker = new Worker('/js/with-worker/exif-worker.js');
// We have to send image requests through this proxy due to CORS limitations:
const imageFetchPrefix = 'https://res.cloudinary.com/demo/image/fetch/';
// Necessary elements we need to select:
const imageFetchPanel = document.getElementById('image-fetch');
const imageExifDataPanel = document.getElementById('image-exif-data');
const exifDataPanel = document.getElementById('exif-data');
const imageInput = document.getElementById('image-url');
// What to do when the form is submitted.
document.getElementById('image-form').addEventListener('submit', event => {
// Don't let the form submit by default:
event.preventDefault();
// Send the image URL to the web worker on submit:
exifWorker.postMessage(`${imageFetchPrefix}${imageInput.value}`);
});
// This listens for the Exif metadata to come back from the web worker:
exifWorker.addEventListener('message', ({ data }) => {
// This populates the Exif metadata viewer:
exifDataPanel.innerHTML = data.message;
imageFetchPanel.style.display = 'none';
imageExifDataPanel.style.display = 'block';
});
此代码在主线程上运行,它会设置表单以将图片网址发送到
Web Worker。然后,Web Worker 代码以 importScripts
开头
语句,用于加载外部 exif-reader
脚本,然后设置
将消息发送到主线程:
// exif-worker.js
// Import the exif-reader script:
importScripts('/js/with-worker/exifreader.js');
// Set up a messaging pipeline to send the Exif data to the `window`:
self.addEventListener('message', ({ data }) => {
getExifDataFromImage(data).then(status => {
self.postMessage(status);
});
});
这段 JavaScript 代码会设置消息传递管道,这样一来,当用户
在提交带有 JPEG 文件网址的表单时,网址到达 Web Worker。
接下来,下面这段代码从 JPEG 文件中提取 Exif 元数据,
构建一个 HTML 字符串,并将该 HTML 发送回 window
,最终
显示给用户:
// Takes a blob to transform the image data into an `ArrayBuffer`:
// NOTE: these promises are simplified for readability, and don't include
// rejections on failures. Check out the complete web worker code:
// https://glitch.com/edit/#!/exif-worker?path=js%2Fwith-worker%2Fexif-worker.js%3A10%3A5
const readBlobAsArrayBuffer = blob => new Promise(resolve => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.readAsArrayBuffer(blob);
});
// Takes the Exif metadata and converts it to a markup string to
// display in the Exif metadata viewer in the DOM:
const exifToMarkup = exif => Object.entries(exif).map(([exifNode, exifData]) => {
return `
<details>
<summary>
<h2>${exifNode}</h2>
</summary>
<p>${exifNode === 'base64' ? `<img src="data:image/jpeg;base64,${exifData}">` : typeof exifData.value === 'undefined' ? exifData : exifData.description || exifData.value}</p>
</details>
`;
}).join('');
// Fetches a partial image and gets its Exif data
const getExifDataFromImage = imageUrl => new Promise(resolve => {
fetch(imageUrl, {
headers: {
// Use a range request to only download the first 64 KiB of an image.
// This ensures bandwidth isn't wasted by downloading what may be a huge
// JPEG file when all that's needed is the metadata.
'Range': `bytes=0-${2 ** 10 * 64}`
}
}).then(response => {
if (response.ok) {
return response.clone().blob();
}
}).then(responseBlob => {
readBlobAsArrayBuffer(responseBlob).then(arrayBuffer => {
const tags = ExifReader.load(arrayBuffer, {
expanded: true
});
resolve({
status: true,
message: Object.values(tags).map(tag => exifToMarkup(tag)).join('')
});
});
});
});
读起来有点用,但对于 Web Worker,这也是一个相当复杂的用例。
然而,这些成果是值得的,而不仅仅局限于此用例。
您可以使用 Web Worker 执行各种操作,例如隔离 fetch
调用
和处理响应、处理大量数据而不阻塞
主线程,这仅供新手使用。
在改进 Web 应用的性能时,请开始考虑 可在 Web Worker 环境中合理完成的任何任务。好处可能是 并为您的网站带来更出色的整体用户体验。