这个前端黑科技可能是YouTube比B站、优酷、爱奇艺加载快的原因
zhezhongyun 2025-01-13 19:11 112 浏览
↑更多精彩,请点击上方蓝字关注我们!
“为什么同样是视频网站,Youtube 感觉加载很快、很轻,B 站、优酷、爱奇艺就感觉慢且重呢?”这是知乎上面的一个浏览次数很高的帖子,而在下面,有人就回答说
虽然我们并不确定这是否是主要因素,但是Service Worker对于前端性能优化的增益效果足以可见了。
之前在上篇《超过100位程序员大佬:这是现代前端性能优化必会的黑科技》中我们已经为大家介绍了Service Worker的概览,以及生命周期。今天我们将在下篇内容里为大家介绍service worker与APIs、使用场景以及小贴士。
APIs
Service Worker依赖于包括cache和fetch在内的许多相关的浏览器API来使得网页应用功能更加丰富,越来越接近原生应用的体验。(注:这里的cache和fetch是指浏览器接口,与上文的Service Worker的生命周期事件注意区分)
Cache & Fetch API
作为一个网页应用,除了前端无法控制的服务接口,最重要的无非是获取和存储资源了,fetch和cache两个接口作为现代浏览器的重要接口,Service Worker的fetch事件的响应几乎可以说是重度依赖于他们。如果浏览器版本稍老一些,可能还需要使用腻子脚本(polyfill)来提供这两个接口。
通过它们,Service Worker可以在截取页面的请求后,修改请求的参数内容,修改请求路径(注意跨域),延迟响应,修改响应内容,使用缓存内容伪造响应内容,甚至只用缓存构造一个完全不再依赖于网络的页面等等。
cache
添加资源到缓存对象中:
self.addEventListener('install', function(event) {
// In install event, cache the resources first
event.waitUntil(
caches.open('my-cache-identifier') // Open/create a cache with identifier
.then(function(cache) {
console.log('Opened cache');
return cache.addAll([
'/',
'/styles/main.css',
'/script/main.js'
]); // Cache the major HTML, CSS, JS file
})
);
});
需要注意,一个缓存对象可以添加多个资源路径->响应结果。
fetch
Fetch API*是浏览器新的API标准,在Service Worker中通常被用于在FetchEvent*中转发请求。
当网页正在发送HTTP请求,触发了Service Worker的fetch事件,我们就能在劫持HTTP包中的内容,按照需要进行读取或者修改,然后再继续发送或者转发给其他目标。
通常有以下操作流程或其中一部分:
- 检查请求目标是否已被缓存,如果已存在直接回复缓存内容;
- 拆包分析请求内容,筛选请求,阻止某些请求被发送,或者修改其内容;
- 使用fetch API发送请求,或者转发请求;
- 当得到响应,拆包检查响应状态,类型或者其他的HTTP头,按照需要筛选或者修改回复体的内容;
- 按需缓存响应结果;
流程代码示例:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
存储 & 通信API
Fetch是通信用的API, cache是存储用的。但他们通常适用于“代理网页的网络请求”和 “缓存网页的资源”。
如果Service Worker还有其他的通信需求(如直接和页面通信,或者其他的Web Worker通信),或者存储不同需求的数据(如页面层数据,浏览器层数据,更大量的数据等),还有以下常用的API。
进程间通信
上文多次提及Web Worker, Service Worker与常规的Web Worker都是独立于渲染上下文的独立线程,所以它们都是无法直接操作DOM或者window对象的。如果我们有和其他Worker或者页面交互的需求,可以使用 postMessage* API和message事件来进行进程/线程间通信。
- 在页面的主线程创建消息频道 MessageChannel*,使用 postMessage向频道上发送消息并且监听上面来的message事件:
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new Promise(function(resolve, reject) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
navigator.serviceWorker.controller.postMessage(message,
[messageChannel.port2]);
});
}
- 在Service Worker中,使用Client对象上的 Client.postMessage*来发送消息, 并且监听 message Service Worker自己的消息事件:
// Consume the message from host thread (or other Workers)
addEventListener('message', (event) => {
console.log(`The client sent me a message: ${event.data}`);
});
{
// Send message to host thread (or other Workers)
clients.matchAll(event.clientId).postMessage({
msg: "Hey I just got a fetch from you!",
});
}
发送消息的demo :
https://googlechrome.github.io/samples/service-worker/post-message/index.html
数据存储
Service Worker中也能使用各种新旧浏览器标准下的Web存储API来持久化数据,我们可以依照不同的需要来选择:
- 注意Service Worker的官方标准提到它是完全基于Promise的异步非阻塞 (it's designed to be fully asynchronous),同步的XHR请求和 localStorage (LocalStorage请求都是完全同步的*) 在Service Worker不可以被使用。
- 对于真正需要被长期持久化且在浏览器重启之后也需要被复用的内容,使用IndexedDB*是更加建议的方案,甚至可以在这之上做类似基于数据库的数据同步。
更多Web应用的API
Service Worker作为PWA的核心概念之一,它也是将web应用变得更接近原生应用的出发点。在它之上,更多浏览器特性提供了类似原生应用的支持。
使用场景
Server Worker在PWA之外也有诸多应用,基于它对HTTP请求和响应的强大管理能力,它可以作为多种依赖网络的应用的核心流程管理器。
- 全静态站点如果一个网站只包含静态数据而无需服务, 我们可以缓存所有的html页面,css样式,脚本和图片等资源,来使得这个页面在初次打开后可以被完全地离线访问。(宝可梦图鉴:https://pokedex.org/)
- 预加载为了优化首屏渲染,页面上非必要的资源通常被延迟加载直到它们被需要。这类资源使用Server Worker来加载既可以使得在需要被加载时有良好的体验,又不会影响到首屏性能。(Demo:https://googlechrome.github.io/samples/service-worker/prefetch/index.html) / (Demo prefetch video:https://googlechrome.github.io/samples/service-worker/prefetch-video/index.html)
- 应变响应有时候HTTP请求可能会因为不确定因素失败(如服务器离线,网络中断等),此时为用户提供一个应变的响应比如展示上一次成功加载的资源/数据。(例如:实时数据监测)Service worker可以帮助验证请求是否成功,如果失败就提供应变策略的回复。 (Demo:https://googlechrome.github.io/samples/service-worker/fallback-response/index.html)
- 仿造响应仿造响应是非常有用的。它可以帮助我们隔离部分特定的请求来使用给定的回复,或者我们可以用它来测试一些尚不可用,或者不能稳定重现问题的资源或者REST API.
- 窗口缓存Service Worker来承担缓存数据的责任,页面可以直接使用window.cache来访问缓存。通过窗口缓存作为媒介可以间接实现service worker向页面的数据传递,也可以将Service Worker用作缓存的生产者而页面作为消费者。Demo:https://googlechrome.github.io/samples/service-worker/window-caches/index.html
- ......
小贴士
- 在Service Worker中使用fetch API来转发请求,请求中默认不会包含cookie等中的用户认证信息。如果需要为转发请求附带认证信息, 在fetch请求中添加'credentials'的参数:
- fetch(url, { credentials: 'include'})
- 跨域资源默认是不支持缓存的,需要额外参数。如果目标资源支持CORS,在构建请求需要附带参数 {mode: 'cors'} 。如果目标资源不支持CORS或者不确定, 我们可以使用 non-cors模式,但这会导致"不透明"的响应, 意味着Service Worker不能判断响应中的状态,不透明的结果被缓存后仍被页面消费成non-cors的响应。
- cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) { return new Request(urlToPrefetch, { mode: 'no-cors' });})).then(function() { console.log('All resources have been fetched and cached.');});
- 30X的HTTP状态码尚不支持离线请求重定向, 这是一个已知的issue(https://github.com/w3c/ServiceWorker/issues/1457)。建议在官方支持离线重定向前,根据你的使用场景寻找其他方案。
- 在使用Service Worker代理HTTP的响应体时,务必记住clone* response,而不要直接消费掉响应体。原因是HTTP response是一个流, 它的内容只能被消费一次。只要我们仍然希望既能让浏览器正确地获得响应体中的内容,又能是它被缓存或者在Service Worker作内容检查,请不要忘记复制一个响应体。
网址导航:
- Fetch API:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
- FetchEvent:https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent
- postMessage:https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
- MessageChannel:https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
- Client.postMessage:https://developer.mozilla.org/en-US/docs/Web/API/Client/postMessage
- LocalStorage请求都是完全同步的:https://stackoverflow.com/questions/20231163/is-html5-localstorage-asynchronous
- IndexedDB:https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
- clone:https://fetch.spec.whatwg.org/#dom-response-clone
引用:
- Chrome官方在线demo:https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker
- Service Worker简介:https://developers.google.com/web/fundamentals/primers/service-workers
- Service Worker API - MDN:https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
- W3C官方标准:https://w3c.github.io/ServiceWorker/
- Service Worker相关的资源:https://jakearchibald.github.io/isserviceworkerready/resources.html#moar
相关推荐
- perl基础——循环控制_principle循环
-
在编程中,我们往往需要进行不同情况的判断,选择,重复操作。这些时候我们需要对简单语句来添加循环控制变量或者命令。if/unless我们需要在满足特定条件下再执行的语句,可以通过if/unle...
- CHAPTER 2 The Antechamber of M de Treville 第二章 特雷维尔先生的前厅
-
CHAPTER1TheThreePresentsofD'ArtagnantheElderCHAPTER2TheAntechamber...
- CHAPTER 5 The King'S Musketeers and the Cardinal'S Guards 第五章 国王的火枪手和红衣主教的卫士
-
CHAPTER3TheAudienceCHAPTER5TheKing'SMusketeersandtheCardinal'SGuard...
- CHAPTER 3 The Audience 第三章 接见
-
CHAPTER3TheAudienceCHAPTER3TheAudience第三章接见M.DeTrévillewasatt...
- 别搞印象流!数据说明谁才是外线防守第一人!
-
来源:Reddit译者:@assholeeric编辑:伯伦WhoarethebestperimeterdefendersintheNBA?Here'sagraphofStea...
- V-Day commemorations prove anti-China claims hollow
-
People'sLiberationArmyhonorguardstakepartinthemilitaryparademarkingthe80thanniversary...
- EasyPoi使用_easypoi api
-
EasyPoi的主要特点:1.设计精巧,使用简单2.接口丰富,扩展简单3.默认值多,writelessdomore4.springmvc支持,web导出可以简单明了使用1.easypoi...
- 关于Oracle数据库12c 新特性总结_oracle数据库12514
-
概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...
- 【开发者成长】JAVA 线上故障排查完整套路!
-
线上故障主要会包括CPU、磁盘、内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍。同时例如jstack、jmap等工具也是不囿于一个方面的问题...
- 使用 Python 向多个地址发送电子邮件
-
在本文中,我们将演示如何使用Python编程语言向使用不同电子邮件地址的不同收件人发送电子邮件。具体来说,我们将向许多不同的人发送电子邮件。使用Python向多个地址发送电子邮件Python...
- 提高工作效率的--Linux常用命令,能够决解95%以上的问题
-
点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...
- linux常用系统命令_linux操作系统常用命令
-
系统信息arch显示机器的处理器架构dmidecode-q显示硬件系统部件-(SMBIOS/DMI)hdparm-i/dev/hda罗列一个磁盘的架构特性hdparm-tT/dev/s...
- 小白入门必知必会-PostgreSQL-15.2源码编译安装
-
一PostgreSQL编译安装1.1下载源码包在PostgreSQL官方主页https://www.postgresql.org/ftp/source/下载区选择所需格式的源码包下载。cd/we...
- Linux操作系统之常用命令_linux系统常用命令详解
-
Linux操作系统一、常用命令1.系统(1)系统信息arch显示机器的处理器架构uname-m显示机器的处理器架构uname-r显示正在使用的内核版本dmidecode-q显示硬件系...
- linux网络命名空间简介_linux 网络相关命令
-
此篇会以例子的方式介绍下linux网络命名空间。此例中会创建两个networknamespace:nsa、nsb,一个网桥bridge0,nsa、nsb中添加网络设备veth,网络设备间...
- 一周热门
- 最近发表
-
- perl基础——循环控制_principle循环
- CHAPTER 2 The Antechamber of M de Treville 第二章 特雷维尔先生的前厅
- CHAPTER 5 The King'S Musketeers and the Cardinal'S Guards 第五章 国王的火枪手和红衣主教的卫士
- CHAPTER 3 The Audience 第三章 接见
- 别搞印象流!数据说明谁才是外线防守第一人!
- V-Day commemorations prove anti-China claims hollow
- EasyPoi使用_easypoi api
- 关于Oracle数据库12c 新特性总结_oracle数据库12514
- 【开发者成长】JAVA 线上故障排查完整套路!
- 使用 Python 向多个地址发送电子邮件
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- opacity 属性 (32)
- transition 属性 (33)