前端开发的workers——web workers、share workers和service workers

(85) 2024-07-13 10:01:01

文章目录

    • 突破单线程的瓶颈WEB WORKERS
      • 兼容性
      • 私有worker
        • “杀死”workers
        • 错误调试
        • 异步在worker的表现
      • 共享的shareWorkers
    • service worker
      • 使用
      • 注册一个service worker
      • 使用servers worker
        • 设置缓存和缓存数据
        • 使用缓存
        • 小结
    • 参考文献

突破单线程的瓶颈WEB WORKERS

web worker就是一个后台执行JS文件的方法,能够给前端传递信息,前端也可以传递信息给web wokers.

  • web workers是一个全新的上下文,与创建它的线程无关。
  • 不可以执行dom操作
  • 没有window这个对象
  • 通过postMessage传递消息

兼容性

if (window.Worker) { 
   // 是否可以使用workers ... } 

私有worker

index.js

if (window.Worker) { 
    var myWoker = new Worker('./work.js') myWoker.postMessage('send message'); myWoker.onmessage = function(e) { 
    console.log('Message received from worker',e.data); } } 

work.js

onmessage = function(e) { 
    console.log('Message received from main script'); this.console.log(e.data) console.log('Posting message back to main script'); postMessage('I get'); } 

输出情况
前端开发的workers——web workers、share workers和service workers (https://mushiming.com/)  第1张
使用web workers还是比较简单的。

  1. 对于使用worker的线程而言:
    首先就是先注册一个workersnew Worker('path')
    然后可以发送数据了
    如果需要接受worker返回的数据,就需要注册一个监听事件onmessage
  2. 对于work.js而言
    首先需要写好一个监听数据请求的事件,当然也可以不用监听直接写好执行数据,这里就看需求。
    如果设置了监听事件,第一个参数会包含发送过来的数据:如实例中的e.data
    当然,worker也可以使用postMessage主动的发送信息给主线程。
“杀死”workers

不想要workers怎么办?直接停掉它。terminate()
myWorker.terminate();
当然,如果workers不想干活了,也可以“罢工”
直接执行这个函数close();即可

错误调试

你当然可以 给workers添加错误回调,如监听数据接受的事件一样,只需要在worker设置好监听即可。

onerror = function(e) { 
    e.message// 错误事件 e.filename// 错误文件 e.lineno // 错误行号 } 
异步在worker的表现
onmessage = function(e) { 
    var a=function () { 
    postMessage('I get'+new Date()); setTimeout(a, 1000); } setTimeout(a, 1000); } 

前端开发的workers——web workers、share workers和service workers (https://mushiming.com/)  第2张
我们给主线程也添加异步事件。

var a1 = function () { 
    console.log('主线程'+new Date()); setTimeout(a1, 2000); } if (window.Worker) { 
    var myWoker = new Worker('./work.js') myWoker.postMessage('send message'); myWoker.onmessage = function(e) { 
    console.log('Message received from worker',e.data); } setTimeout(a1, 2000); } 

前端开发的workers——web workers、share workers和service workers (https://mushiming.com/)  第3张
符合多线程的表现,我们弄的复杂点,让主线程的执行超过1秒,看看是否会阻塞workers。
我们在主线程中添加了一个超过一秒的复杂运算,执行之后的情况如下。
前端开发的workers——web workers、share workers和service workers (https://mushiming.com/)  第4张
首先说下workers,主线程的阻塞并不会影响workers的异步执行,只是会影响它的输出,因为它是通过发送消息给主线程输出的,所以会等主线程执行完,才会按照顺序执行workers 返回的事件队列。
对于主线程而言,阻塞已经影响到了异步了,因为每一次输出已经超过2秒
其实如果workers也有复杂的运算,表现也是和主线程一样的。

这里也可以看出来为什么workers不能操作DOM。
如果workers可以操作DOM的,那么很容易无法更新到最新的状态。

共享的shareWorkers

共享workers,页面之间需要符合同源策略。
index.js

if (!!window.SharedWorker) { 
    var myWorker = new SharedWorker("work.js"); myWorker.port.onmessage = function(e) { 
    console.log('Message received from worker'); } } 

和workes的区别在于多一个端口调用。
必须设置一个onmessage或者port.start()

什么时候使用start?
当我们需要addEventListener()时候
如何使用?
父页面直接执行 myWorker.port.start()
workers执行 port.start()完成打开端口即可

这是为了与后续的work.js的connect相呼应,因为相当于注册一个端口。
在workers是隐性的,并不是必需进行这步。
如果使用start打开端口,就需要在work.js中调用相同的方法。port.start()

我们来看看work.js的代码

var clients = []; onconnect = function(e) { 
    var port = e.ports[0]; clients.push(port); port.onmessage=function(e) { 
    clients.forEach((item,i)=>{ 
    item.postMessage(e.data) }) } } 

这里不在是onmessage来设置监听事件了,我们使用一个变量,将所有的注册端口保存起来,这就是共享的意义,一个worker相当于一个单例,所以页面访问的是同一个上下文。
如果想要广播所有端口,就可以像例子中遍历端口发送数据。
如果你需要对某个特定的页面端口进行特别处理,你可以通过父页面的传递信息约定一个数据,进行判断处理事件。
注意:需要页面是同源的情况下才能访问。

service worker

Service worker是一个注册在指定源和路径下的事件驱动worker。
它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。
常见于网络不可用的情况下

·service worker同样是workers的另一个类型。但是由于目前支持度不是很高,所以还是遇到很大的兼容性的问题,得不到广泛的使用。

  • 同步API(如XHR和localStorage)不能在service worker中使用。
  • 出于安全考量,Service workers只能由·HTTPS承载

如果一个网页没有了网络,那就将失去所有意义。
所以为了解决这个问题,就需要一个线程脚本,在没有网络连接的时候来控制网页。这样就导致离线页面和service worker的出现。有了这一功能,也代表着网页APP有了与原生APP叫板的底气。

  • 缺点:非常明显,开启service worker可能会导致浏览器的缓存数据大大增加。

使用

生命周期:
前端开发的workers——web workers、share workers和service workers (https://mushiming.com/)  第5张

  1. 下载
  2. 安装
  3. 激活

用户首次访问service works控制的网站和网址的时候,service works会立刻下载。

之后每24小时就会被下载以此,期间可能频繁更新,不过每24小时一定会被下载一次,以避免不良脚本长时间生效。

如果这是首次启用service worker,页面会首先尝试安装,安装成功后它会被激活。

如果新版本的service worker已经下载完但是正在安装,但是不会被激活,这个时候称为 worker in waiting。
直到所有已经加载页面不再使用旧的service worker 的时候,才会激活新的service worker。新激活的service worker称为active worker

注册一个service worker

·'/sw-test/'·,表示 app 的 origin 下的所有内容。如果你留空的话,默认值也是这个值, 我们在指定只是作为例子。

if ('serviceWorker' in navigator) { 
    // 读取js,进行注册 navigator.serviceWorker.register('/sw-test/sw.js', { 
    scope: '/sw-test/' }).then(function(reg) { 
    if(reg.installing) { 
   // 这里有判断service worker状态 console.log('Service worker installing'); } else if(reg.waiting) { 
    console.log('Service worker installed'); } else if(reg.active) { 
    console.log('Service worker active'); } }).catch(function(error) { 
    // registration failed console.log('Registration failed with ' + error); }); } 

使用servers worker

设置缓存和缓存数据

进行了上面的注册,就可以开始使用service worker的API了

// sw.js self.addEventListener('install', function(event) { 
    event.waitUntil( caches.open('v1').then(function(cache) { 
    return cache.addAll([ '/sw-test/', '/sw-test/index.html', '/sw-test/style.css', '/sw-test/app.js', '/sw-test/image-list.js', '/sw-test/star-wars-logo.jpg', '/sw-test/gallery/bountyHunters.jpg', '/sw-test/gallery/myLittleVader.jpg', '/sw-test/gallery/snowTroopers.jpg' ]); }) ); }); 

我们先讨论首次加载的情况
这里添加的是安装的监听事件。
waitUntil是确保service worker安装前,读取到所有的数据。
caches.open()方法,是创建一个缓存v1区域,保存下面的文件。这里使用的是promise来实现。
service worker安装完成之后,就开始变成激活状态。
虽然都是缓存,但是service worker不允许使用localstorage

使用缓存

添加fetch事件,进行操作。

self.addEventListener('fetch', function(event) { 
    event.respondWith(caches.match(event.request).then(function(response) { 
    // caches.match() always resolves // but in case of success response will have value if (response !== undefined) { 
    return response; } else { 
    return fetch(event.request).then(function (response) { 
    // response may be used only once // we need to save clone to put one copy in cache // and serve second one let responseClone = response.clone(); caches.open('v1').then(function (cache) { 
    cache.put(event.request, responseClone); }); return response; }).catch(function () { 
    return caches.match('/sw-test/gallery/myLittleVader.jpg'); }); } })); }); 

当service worker控制的资源被请求时候,就会触发fetch事件。这些资源包含了指定的scope文档。
事件上的EVENT上的respondWith劫持HTTPS的请求,劫持之后你就可以进行任何的操作
例子中caches.match是用于匹配本地资源是否存在这个文件。
然后判断本地缓存是否有网络请求的响应报文,如果有则使用本地资源缓存,如果没有则使用fetch进行请求,当然也可以使用xht,如果使用后者,你还需要配置路径和方法,而fetch是封装好的API,只需要传入请求文段即可发送请求。
fetch这个方法本身是支持promise的,所以是异步进行请求的。
如果想要下次不需要网络请求,你可以将请求报文段保存到缓存,这样下次请求能够直接使用本地的缓存文件了。
最后如果不出意外,请求会返回一些数据,你可以将他们保存到本地,然后再返回给客户端。
cache.put(event.request, responseClone);

小结

其实service worker更新一个中间代理服务器。它特别的地方是离线状态也可以访问,如果存在你需要的资源就可以返回给你,这样达到离线访问web app的目的。
当然,这种API自然对资源管理要求更加严格,如果滥用很可能会导致缓存过大,打开一个网页会让计算机占的内存变大等问题。目前支持度主要是谷歌的CHROM 和火狐的FIRFOX。这些都是默认关闭的,需要用户自己打开才能使用这个API,所以目前还处在测试的环节。
不过抛开这些短板来看,web app或许真的可能会代替native app。

  • 应用场景
    1. 响应推送:启动一个service work,获取服务器推送的消息,不会影响用户体验
    2. 后台同步:启动一个service work,更新服务器的数据,不会影响用户体验
    3. 自定义模板用于特定URL模式:因为service worker能够劫持HTTP请求,所以可以自己自定义格式进行处理。
    4. 性能增强:我们可以对用户的预处理进行预请求,比如图片的懒加载,可以先使用service worker保存前几页的数据,这样用户交互的时候,就会减少等待时间。

其实上面的功能,普通的web workers都可以实现,但是由于service work已经封装好了API,如劫持HTTP请求的方法,对上面的场景实现起来非常方便。

参考文献

MDN WEB WORKERS

THE END

发表回复