百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

这个前端黑科技可能是YouTube比B站、优酷、爱奇艺加载快的原因

zhezhongyun 2025-01-13 19:11 130 浏览

↑更多精彩,请点击上方蓝字关注我们!


“为什么同样是视频网站,Youtube 感觉加载很快、很轻,B 站、优酷、爱奇艺就感觉慢且重呢?”这是知乎上面的一个浏览次数很高的帖子,而在下面,有人就回答说


虽然我们并不确定这是否是主要因素,但是Service Worker对于前端性能优化的增益效果足以可见了。

之前在上篇超过100位程序员大佬:这是现代前端性能优化必会的黑科技中我们已经为大家介绍了Service Worker的概览,以及生命周期。今天我们将在下篇内容里为大家介绍service worker与APIs、使用场景以及小贴士。



APIs


Service Worker依赖于包括cachefetch在内的许多相关的浏览器API来使得网页应用功能更加丰富,越来越接近原生应用的体验。(注:这里的cache和fetch是指浏览器接口,与上文的Service Worker的生命周期事件注意区分)


Cache & Fetch API

作为一个网页应用,除了前端无法控制的服务接口,最重要的无非是获取和存储资源了,fetchcache两个接口作为现代浏览器的重要接口,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来持久化数据,我们可以依照不同的需要来选择:



  1. 注意Service Worker的官方标准提到它是完全基于Promise的异步非阻塞 (it's designed to be fully asynchronous),同步的XHR请求和 localStorage (LocalStorage请求都是完全同步的*) 在Service Worker不可以被使用。
  2. 对于真正需要被长期持久化且在浏览器重启之后也需要被复用的内容,使用IndexedDB*是更加建议的方案,甚至可以在这之上做类似基于数据库的数据同步。




更多Web应用的API

Service Worker作为PWA的核心概念之一,它也是将web应用变得更接近原生应用的出发点。在它之上,更多浏览器特性提供了类似原生应用的支持。





使用场景


Server Worker在PWA之外也有诸多应用,基于它对HTTP请求和响应的强大管理能力,它可以作为多种依赖网络的应用的核心流程管理器。


  1. 全静态站点如果一个网站只包含静态数据而无需服务, 我们可以缓存所有的html页面,css样式,脚本和图片等资源,来使得这个页面在初次打开后可以被完全地离线访问。(宝可梦图鉴:https://pokedex.org/
  2. 预加载为了优化首屏渲染,页面上非必要的资源通常被延迟加载直到它们被需要。这类资源使用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
  3. 应变响应有时候HTTP请求可能会因为不确定因素失败(如服务器离线,网络中断等),此时为用户提供一个应变的响应比如展示上一次成功加载的资源/数据。(例如:实时数据监测)Service worker可以帮助验证请求是否成功,如果失败就提供应变策略的回复。 (Demo:https://googlechrome.github.io/samples/service-worker/fallback-response/index.html
  4. 仿造响应仿造响应是非常有用的。它可以帮助我们隔离部分特定的请求来使用给定的回复,或者我们可以用它来测试一些尚不可用,或者不能稳定重现问题的资源或者REST API.
  5. 窗口缓存Service Worker来承担缓存数据的责任,页面可以直接使用window.cache来访问缓存。通过窗口缓存作为媒介可以间接实现service worker向页面的数据传递,也可以将Service Worker用作缓存的生产者而页面作为消费者。Demo:https://googlechrome.github.io/samples/service-worker/window-caches/index.html
  6. ......




小贴士


  • 在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

相关推荐

Python入门学习记录之一:变量_python怎么用变量

写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

python变量命名规则——来自小白的总结

python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...

Python入门学习教程:第 2 章 变量与数据类型

2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...

绘制学术论文中的“三线表”具体指导

在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...

Python基础语法知识--变量和数据类型

学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...

一文搞懂 Python 中的所有标点符号

反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...

Python变量类型和运算符_python中变量的含义

别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...

从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序

在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...

Python中下划线 ‘_’ 的用法,你知道几种

Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...

解锁Shell编程:变量_shell $变量

引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...

一文学会Python的变量命名规则!_python的变量命名有哪些要求

目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...

更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for

src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...

C++第五课:变量的命名规则_c++中变量的命名规则

变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....

Rust编程-核心篇-不安全编程_rust安全性

Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...

探秘 Python 内存管理:背后的神奇机制

在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...