前端性能优化之 web worker(附源码)

1,693 阅读5分钟

前端性能优化之web woker

系列文章:

前端项目优化之 取消请求

什么是web worker

因为js是单线程,往往不能满足需求😒,所以提供了web worker.

Web Worker 使得在一个独立于 Web 应用程序主执行线程后台线程中运行脚本操作成为可能。

这样做的好处是可以在独立线程中执行费时的处理任务,使主线程(通常是 UI 线程)的运行不会被阻塞/放慢。

本文主要介绍:WorkerSharedWorkerserviceWorker的基本使用、注意事项及总结

一、Woker

  • 用于处理单一页面耗时操作
  • 与创建它的页面脚步对应,不能被其它页面共享
  • 通常用于复杂计算数据处理、异步
  • 在主动终止、页面关闭之前 一致运行
  • 不能访问window、document、parent等全局对象
  • 可以使用XMLHttpRequest、fetch、setTimeout、setInterval
  • 可以使用importScripts加载外部脚本

1. Worker的使用

主线程

// 创建worker
const worker = new Worker('worker.js');

// 向worker发送消息
worker.postMessage('hello');

// 监听worker发送的消息
worker.onmessage = function(event) {
  console.log(event.data);
}

// 监听worker的错误
worker.onerror = function(event) {
  console.log(event.message);
}

worker.onmessageerror = function(event) {
  console.log(event.message);
}

// 终止worker
console.log('worker terminated');
worker.terminate();

worker.js

// 监听主线程发送的消息
self.onmessage = function(event) {
  // 向主线程发送消息
  self.postMessage('hello');
  setTimeout(() => {
    self.postMessage('world');
    // 终止worker
    self.close();
  }, 1000)
}

2. 终止worker

  • 主线程调用worker.terminate()
  • worker调用self.close()
  • worker线程终止、页面关闭之前 一直运行(所有如果不是太复杂的任务,不建议使用worker)

二、SharedWorker

  • 可以被多个页面共享 (同源)
  • 可以使用XMLHttpRequest、fetch进行异步请求
  • 可以使用setTimeout、setInterval进行定时操作
  • 可以使用importScripts加载外部脚本

1. 语法和参数说明

mdn参考:SharedWorker 语法

var myWorker = new SharedWorker(aURL, name);
var myWorker = new SharedWorker(aURL, options);

参数

  • aURL:worker脚本的URL
  • name:worker的名称,可选 (注册多个页面时注意name一致,不然会有问题
  • options:worker的选项,可选,包括name、type、credentials、data

2. 具体使用

主线程

// 创建SharedWorker
const worker = new SharedWorker('worker.js',{name: 'woker1'});

// 向SharedWorker发送消息
worker.port.postMessage('hello');

// 监听SharedWorker发送的消息
worker.port.onmessage = function(event) {
  console.log(event.data);
}

// 监听SharedWorker的错误
worker.port.onerror = function(event) {
  console.log(event.message);
}

// 终止SharedWorker
console.log('SharedWorker terminated');
worker.port.close();

worker.js

// 保存 多个页面的连接
const connections = [];

self.addEventListener('connect', function(event) {
  const port = event.ports[0];
  connections.push(port);

  port.start();
  port.addEventListener('message', function(e) {
      const message = e.data;
      // 广播到多个页面
      connections.forEach(function(conn) {
          console.log('Sending message to connection:', conn);
          conn.postMessage('Received: ' + message);
      });
  });
});

vue中的使用

image.png 下面vue中是运行效果 shareworker.gif

3.注意事项

  • SharedWorker脚本的打印在 chrome://inspect/#workers
  • 多个页面注册共用一个name,才能正确响应
  • 页面同源 image.png

image.png

三、service Worker

1. 介绍

本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器

  • 作用:缓存资源,离线使用

  • 生命周期:install -> waiting -> activate -> fetch -> 是否命中缓存(返回)

  • 使用场景:缓存静态资源,离线使用,请求拦截,应用无网络体验优化

  • 缓存逻辑:

    1. 监听页面请求(xhr、fetch都可以)
    2. 判断请求是否是缓存列表中的路径,是再进行处理
    3. 匹配缓存,查看缓存是否过期,未过期直接返回缓存
    4. 过期,重新发起请求,并把响应结果推到缓存,并返回

2. service Worker基础使用

主线程

const registerServiceWorker = async () => {
  if ("serviceWorker" in navigator) {
    try {
      const registration = await navigator.serviceWorker.register("./sw.js", {
        scope: "./",
      });
      if (registration.installing) {
        console.log("正在安装 Service worker", registration);
      } else if (registration.waiting) {
        console.log("已安装 Service worker installed", registration);
      } else if (registration.active) {
        console.log("激活 Service worker", registration);
      }
      registration.addEventListener("updatefound", () => {
        console.log("更新 Service worker", registration);
      });
  } catch (error) {
    console.error(`注册失败:${error}`);
  }
}
};
registerServiceWorker()

service worker

  1. install:浏览器检测到新serviceworker文件或内容更新,会下载并安装,次阶段可做预缓存,注意缓存失败,会导致service worker也安装失败。
  2. active: istall完成,进入激活阶段,一般可用于清理旧缓存
  3. fetch: 拦截与之建立连接的网页的所有请求,进入做缓存策略
  4. ""
// sw.js
const CACHE_NAME = 'cache-v1.0';
const expirationTime = 1000 * 60 * 60 * 24 * 7; // 缓存过期时间为一周
// 安装 Service Worker
const cssList = [
    
]
const jsList = [
    '/docs/guide/js/worker/js/test.js',
    '/api/user/getJSON'
]
const urlsToCache = [...jsList, ...cssList]

// 监听 install 事件
self.addEventListener('install', event => {
  try {
    event.waitUntil(
      caches.open(CACHE_NAME)
        .then(cache => {
          // 如果 urlsToCache 存在不可访问路径会报错
          return cache.addAll(urlsToCache);
        })
        .then(() => self.skipWaiting()).catch(err => {
          console.log('Service Worker install error', err);
        })
    );
  } catch (error) {
  }
});

// 监听 activate 事件
self.addEventListener('activate', event => {
  console.log('Service Worker activate');
  event.waitUntil(
    caches.keys()
      .then(cacheNames => {
        return Promise.all(
          cacheNames.map(cacheName => {
            if (cacheName !== CACHE_NAME) {
              return caches.delete(cacheName);
            }
          })
        );
      })
      .then(() => self.clients.claim())
  );
});

// 监听 fetch 事件
self.addEventListener('fetch', event => {
  const requestUrl = new URL(event.request.url);
});

3. 双向通信实现

// 主线程
// index.html
const sw = navigator.serviceWorker;
const registration = await sw.register("./sw.js", {
scope: "./",
});
sw.addEventListener("message", function (e){
  console.log("收到消息", e.data);
})
sw.controller.postMessage({
  msg: "Hello from the page!",
  timestamp: Date.now()
});

// ___________________________________________________________________//
// sw.js
self.addEventListener('message', event => {
  console.log('Worker received message:', event.data);

  self.clients.matchAll().then(clients => {
    console.log('Service Worker fetch', clients);
    clients.forEach(client => {
      client.postMessage({
        msg: "Hello from your Service Worker!",
        timestamp: Date.now()
      });
    });
  });
})

4.缓存实战

现在页面中请求

// index.html
fetch("http://localhost:3001/api/user/getJSON").then((res) => {
  console.log("fetch success: ", res.data);
})
// sw.js
const CACHE_NAME = 'cache-v1.0.1';
const expirationTime = 1000 * 60 * 60 * 24 * 7; // 缓存过期时间为一周
const urlsToCache = ['/api/user/getJSON']
// 监听 fetch 事件
self.addEventListener('fetch', event => {
  const requestUrl = new URL(event.request.url);
  if (urlsToCache.includes(requestUrl.pathname )) {
    event.respondWith(
      caches.open(CACHE_NAME)
        .then(cache => {
          return cache.match(event.request)
            .then(response => {
              if (response) {
                const metadata = response.headers.get('x-metadata');
                const cacheTime = metadata ? parseInt(metadata) : 0;
                if (!cacheTime || cacheTime && (Date.now() - cacheTime) > (expirationTime)) { // 如果缓存过期则重新请求
                  console.log('缓存过期', response);
                  return fetch(event.request)
                    .then(fetchResponse => {
                      const headers = new Headers(fetchResponse.headers);
                      headers.append('x-metadata', Date.now());
                      const responseWithMetadata = new Response(fetchResponse.body, { status: fetchResponse.status, statusText: fetchResponse.statusText, headers });
                      cache.put(event.request, responseWithMetadata.clone());
                      return responseWithMetadata;
                    });
                } else { // 否则直接返回缓存
                  return response;
                }
              } else { // 如果缓存中不存在,则先请求资源并缓存
                return fetch(event.request)
                  .then(fetchResponse => {
                    const headers = new Headers(fetchResponse.headers);
                    headers.append('x-metadata', Date.now());
                    const responseWithMetadata = new Response(fetchResponse.body, { status: fetchResponse.status, statusText: fetchResponse.statusText, headers });
                    cache.put(event.request, responseWithMetadata.clone());
                    return responseWithMetadata;
                  });
              }
            });
        })
    );
  }
});

缓存成功networker如下图: image.png 缓存存放位置:

image.png

5.更多实战参考

项目实战可以参考:浏览器缓存之Service Worker

源码与总结

源码:

小易worker

总结

worker: 一般用于复杂、大量的数据计算,不会阻塞主线程,可以提升性能

service worker: 一般用于缓存资源,提升性能,离线访问,无网络情况的基本内容展示

shared worker: 一般用于多页面的通信,可以共享数据,提升性能 此外多页面通信的方式还有:service worker、localStorage、cookie、BroadcastChannel、open、postMessage、websocket等

结语:

如果本文对你有收获,麻烦动动发财的小手,点点关注、点点赞!!!👻👻👻

因为收藏===会了

如果有不对、更好的方式实现、可以优化的地方欢迎在评论区指出,谢谢👾👾👾