前言

JavaScript 采用的是单线程模型,作为一种处理浏览器交互的脚本语言,简单、安全、可靠是最重要的考虑因素,单线程设计让开发人员无需担心多线程并发问题,避免了由多线程并发带来的复杂性和可能的竞态条件,这有助于减少代码错误和提高代码可靠性。

为了避免线程阻塞,JavaScirpt 支持异步编程模式例如回调函数、Promiseasync/await等),这样能够有效地处理异步操作,保持页面响应性。

随着浏览器版本迭代,现代的JavaScript运行时环境(如浏览器和Node.js)也提供了 Web Workers 和 Node.js 的 cluster模块等机制,使得可以利用多线程来处理一些计算密集型任务或者并行处理一些耗时操作,但这些并不是JavaScript的核心编程模型,而且 web worker 的使用有诸多限制比如不能操作 DOM,所以可以说并没有改变 JavaScript 依然是单线程语言的本质。

web worker

后来随着计算机性能的增强,尤其是 CPU 核心数量的增加,单线程模型无法很好的利用多核 CPU的能力,无法发挥出计算优势,于是 H5 规范引入了 web worker ,其中包括 worker、sharedWorker、service worker 三种 worker 类型,后两种属于是 web worker 的特殊类型。

在《javaScript 高级程序设计》中,web worker 被称为“Dedicated Workers”(专用工作者),指在应用程序中用JavaScript进行的多线程操作。

首先我们看下 web worker 的兼容性,可以看到主流的浏览器都支持了,可以放心使用。

讲讲 web worker_缓存

它使用起来和我们习惯的 Js 写法不同,和 iframe 倒是有一点相似之处,比如 都是用 postmessage 发送消息,用 onmessage 监听消息变动。

具体使用方式示例:

1. 创建 Web Worker

要创建一个 Web Worker,首先需要新建一个 JavaScript 文件,该文件将包含你要在后台线程中执行的代码; 然后,通过以下方式在你的主页面中创建一个 Web Worker:

// 创建一个新的 Web Worker
const worker = new Worker('worker.js');

这里的 'worker.js' 是你创建的 JavaScript 文件的路径(必须是网络地址,不能是本地文件)。一旦创建了 Web Worker,它就会在后台运行,而不会阻塞主页面的渲染和用户交互。

2. 与 Web Worker 通信

主页面与 Web Worker 之间可以通过消息传递进行通信。主页面可以向 Web Worker 发送消息,Web Worker 也可以向主页面发送消息。例如,在主页面中向 Web Worker 发送消息的方式如下:

worker.postMessage('Hello from the main page!');

而在 Web Worker 中接收消息的方式是:

// 监听来自主页面的消息
self.onmessage = function(event) {
  console.log('Message from main page:', event.data);
};

3. Web Worker 执行任务

Web Worker 可以执行一些比较耗时的任务,比如大量的数据处理、计算密集型的算法等。在 Web Worker 中,你可以编写 JavaScript 代码来执行这些任务,而不会影响主页面的性能和响应速度。

// worker.js

// 监听来自主页面的消息
self.onmessage = function(event) {
  // 从主页面接收到的消息
  const messageFromMainPage = event.data;

  // 在后台线程中执行任务
  const result = performTask(messageFromMainPage);

  // 将结果发送回主页面
  self.postMessage(result);
};

function performTask(data) {
  // 在这里执行耗时任务
  return data.toUpperCase(); // 示例任务:将接收到的数据转换为大写
}

4. 终止 Web Worker

当你不再需要 Web Worker 时,可以通过调用 terminate() 方法来终止它,释放资源并停止后台任务的执行:

worker.terminate();

service worker

介绍

Service Worker 是一种特殊类型的 Web Worker,它能够在后台执行脚本,但与普通的 Web Worker 不同,Service Worker 主要用于实现一些具有持久性的网络代理,例如拦截和处理网络请求,实现离线缓存等功能, 它经常和 PWA 结合使用以改善 PWA 的性能和用户体验。

  • 运行环境不同:普通的 Web Worker 主要用于在浏览器中执行耗时任务,而 Service Worker 则主要用于管理网络请求和响应,具有更多的网络代理功能。
  • 生命周期长:Service Worker 具有较长的生命周期,并且可以在用户关闭网页后继续运行,用于实现一些离线缓存等功能。
  • 能力更强大:Service Worker 可以拦截网络请求,实现离线缓存、推送通知等功能,而普通的 Web Worker 主要用于执行计算密集型任务。

讲讲 web worker_前端_02

应用场景

Service Worker 可以用于多种场景,其中一些典型的应用包括:

  • 离线缓存:Service Worker 可以拦截网络请求,使得网站在离线状态下依然可以访问缓存的资源,提高了用户体验。
  • 推送通知:Service Worker 可以接收来自服务器的推送通知,并在用户离线时显示通知,这对于实时更新和即时通讯非常有用。
  • 后台同步:Service Worker 可以在后台同步数据,即使用户关闭了网页,也可以持续地进行数据同步操作,保持数据的最新状态。

离线缓存应用

下面是一个简单的 Service Worker 脚本,用于在浏览器离线时缓存页面资源,并在离线状态下提供这些资源:

// service-worker.js

const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', function(event) {
  // 打开缓存并将资源添加到缓存中
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('缓存已打开');
        return cache.addAll(urlsToCache);
      })
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // 如果找到匹配的缓存,直接返回缓存的响应
        if (response) {
          return response;
        }
        // 否则发起网络请求
        return fetch(event.request);
      })
  );
});

在你的 HTML 文件中,注册 Service Worker:

<script>
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
      navigator.serviceWorker.register('/service-worker.js')
        .then(function(registration) {
          console.log('Service Worker 注册成功:', registration);
        })
        .catch(function(error) {
          console.log('Service Worker 注册失败:', error);
        });
    });
  }
</script>

这段代码将会在浏览器离线时,使用 Service Worker 缓存的资源来提供页面内容。

后台同步应用

Service Worker 还可以在后台执行数据同步任务,即使用户关闭了网页,也可以保持数据的同步更新。下面是一个简单的示例代码,用于在 Service Worker 中实现后台同步功能:

// service-worker.js

self.addEventListener('sync', function(event) {
  if (event.tag === 'syncData') {
    event.waitUntil(syncData());
  }
});

function syncData() {
  // 在这里执行后台同步任务
  return fetch('/api/sync')
    .then(function(response) {
      if (!response.ok) {
        throw new Error('同步失败');
      }
      return response.json();
    })
    .then(function(data) {
      console.log('后台同步成功:', data);
    })
    .catch(function(error) {
      console.error('后台同步失败:', error);
    });
}

在主页面或其他需要触发后台同步的地方,可以注册一个后台同步事件:

// 在主页面中注册后台同步事件
if ('serviceWorker' in navigator && 'SyncManager' in window) {
  navigator.serviceWorker.ready.then(function(registration) {
    return registration.sync.register('syncData');
  });
}

当网络连接恢复时,Service Worker 将会触发后台同步事件,并执行指定的后台同步任务。

消息推送应用

讲讲 web worker_Web_03

实现推送通知需要客户端和服务器端的配合。这里我提供一个简单的客户端注册代码示例:

// 在页面中请求用户授权
Notification.requestPermission(function(status) {
  console.log('通知权限状态:', status);
});

// 在注册 Service Worker 之后,订阅推送通知
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.ready.then(function(registration) {
    registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array('your-public-key')
    })
    .then(function(subscription) {
      console.log('订阅成功:', subscription);
    })
    .catch(function(error) {
      console.log('订阅失败:', error);
    });
  });
}

// 将 Base64 编码的公钥转换为 UInt8Array 格式
function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - base64String.length % 4) % 4);
  const base64 = (base64String + padding)
    .replace(/\-/g, '+')
    .replace(/_/g, '/');
  const rawData = window.atob(base64);
  const outputArray = new Uint8Array(rawData.length);
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}

在上面的代码中,你需要替换 your-public-key 为你自己的公钥。

这些示例代码可以帮助你理解如何利用 Service Worker 实现离线缓存和推送通知等功能。实际应用中,你可能需要更多的逻辑来处理通知的显示和交互以及缓存的更新等情况。

sharedWorker

SharedWorker 是 Web Worker 的一种特殊类型,它可以在多个浏览上下文(比如多个窗口、标签页或者框架)之间共享数据和通信。与普通的 Web Worker 不同,SharedWorker 可以被多个浏览上下文所共享,从而实现跨页面的数据共享和通信。

创建 SharedWorker

创建 SharedWorker 的方式与创建普通的 Web Worker 类似,但需要使用特殊的构造函数 SharedWorker

const mySharedWorker = new SharedWorker('shared-worker.js');

在这里,'shared-worker.js' 是 SharedWorker 脚本文件的路径。

在浏览上下文中共享 SharedWorker

不同的浏览上下文(比如不同的窗口、标签页或者框架)可以通过同一个 SharedWorker 实例来共享数据和进行通信。

在每个浏览上下文中,你可以通过 port 属性来和 SharedWorker 进行通信,例如:

// 主页面或者其他浏览上下文中
const mySharedWorker = new SharedWorker('shared-worker.js');
const port = mySharedWorker.port;

// 发送消息到 SharedWorker
port.postMessage('Hello from the main page!');

// 接收来自 SharedWorker 的消息
port.onmessage = function(event) {
  console.log('Message from SharedWorker:', event.data);
};

在 SharedWorker 的脚本中,你可以监听来自不同浏览上下文的连接和消息:

// shared-worker.js

// 监听来自浏览上下文的连接
self.onconnect = function(event) {
  const port = event.ports[0];

  // 接收来自浏览上下文的消息
  port.onmessage = function(event) {
    console.log('Message from client:', event.data);

    // 向浏览上下文发送消息
    port.postMessage('Hello from SharedWorker!');
  };

  // 初始化连接
  port.start();
};

应用场景

SharedWorker 适用于需要在多个浏览上下文之间共享数据和通信的场景,比如协同编辑、聊天应用、多窗口间的数据同步等。

注意事项

  • SharedWorker 脚本的生命周期不受任何窗口或标签页的控制,只有当所有连接到它的浏览上下文都关闭时,SharedWorker 才会被终止。
  • SharedWorker 中的全局上下文对象是 self,而不是 window

总的来说,SharedWorker 提供了一种方便的方式,在多个浏览上下文之间共享数据和进行通信,为开发者提供了更多的灵活性和功能性。