💞💞💞SharedWorker 让你多个页面相互通信

简介: SharedWorker 是一个新的Web Worker API,它允许你在多个页面之间共享一个Worker。 SharedWorker 代表一种特定类型的Worker,可以在多个浏览器上下文中运行,

SharedWorker 是一个新的Web Worker API,它允许你在多个页面之间共享一个Worker

SharedWorker 代表一种特定类型的Worker,可以在多个浏览器上下文中运行,比如多个页面或者多个iframe

什么是 SharedWorker

根据前几篇的了解,大家应该对Worker有了一定的了解,SharedWorkerWorker的一种,它允许你在多个页面之间共享一个Worker

SharedWorker对比于Service Worker要简单很多,同样也是继承与Worker,但是它的生命周期是独立于页面的。

SharedWorker和其他Worker的相同,都是需要同源的,它是通过url来区分是否是同一个SharedWorker

使用 SharedWorker

SharedWorker的使用很简单,只需要在创建Worker的时候,需要传入一个name参数,这个name参数就是SharedWorker的标识,相同的name会共享同一个SharedWorker

const sharedWorker = new SharedWorker('worker.js', 'mySharedWorker');

默认情况下name并不是必须的,这个时候相同的url会共享同一个SharedWorker

const sharedWorker = new SharedWorker('worker.js');

创建SharedWorker之后,我们就可以通过port属性来和SharedWorker进行通信了。

sharedWorker.port.onmessage = (e) => {
   
   
  console.log(e.data);
};

在主线程中,可以通过创建出来的worker对象中的port属性来和SharedWorker进行通信。

使用worker.port.onmessage来监听SharedWorker的消息。

SharedWorker中,可以通过self.onconnect来监听SharedWorker的连接。

在监听事件中会获取到与SharedWorker连接的port,通过这个port就可以和主线程进行通信了。

self.onconnect = (e) => {
   
   
  const port = e.ports[0];

  port.postMessage('hello');
};

通信还是通过postMessage来进行的。

通信

上面的通信只是一个简单的示例,在主线程中创建的SharedWorker,并使用port来和SharedWorker进行通信。

通常情况下,我们会使用onmessage来监听SharedWorker的消息,使用这种方式默认是打开状态,如果使用addEventListener来监听SharedWorker的消息,需要手动调用start方法来开启。

sharedWorker.port.addEventListener('message', (e) => {
   
   
  console.log(e.data);
});

sharedWorker.port.start();

在主线程中,我们可以通过port来实现和SharedWorker的通信。

// 发送消息
sharedWorker.port.postMessage('hello');

// 监听消息
sharedWorker.port.onmessage = (e) => {
   
   
  console.log(e.data);
};

// 或者
sharedWorker.port.addEventListener('message', (e) => {
   
   
  console.log(e.data);
});
sharedWorker.port.start();

SharedWorker中使用方式也是一样的,不同的是,我们需要通过self.onconnect来监听SharedWorker的连接。

self.onconnect = (e) => {
   
   
    const port = e.ports[0];

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

    // 监听消息
    port.onmessage = (e) => {
   
   
        console.log(e.data);
    };

    // 或者

    port.addEventListener('message', (e) => {
   
   
        console.log(e.data);
    });
    port.start();
};

可以看到上面监听的时候,获取port是一个数组,这是因为SharedWorker可以和多个页面进行通信。

每个页面都会有一个port,通过这个portSharedWorker进行通信。

通常情况下我们会记录下所有的port,然后在需要的时候进行通信。

const ports = [];

self.onconnect = (e) => {
   
   
    const port = e.ports[0];
    ports.push(port);

    port.onmessage = (e) => {
   
   
        console.log(e.data);
    };
};

// 发送消息
function sendMessage(message) {
   
   
    ports.forEach((port) => {
   
   
        port.postMessage(message);
    });
}

上面我们的案例中,我们只创建了一个SharedWorker,所以只有一个port,但是如果我们创建了多个SharedWorker,那么就会有多个port

这里收到消息之后浏览器控制台没有任何打印信息,因为SharedWorker的作用域中没有window对象,所以consolealert等方法都是无法使用的。

如果我们需要调试SharedWorker,可以在浏览器地址栏中输入chrome://inspect/#workers,这样就可以看到当前页面中的SharedWorker

image.png

指定页面通信

上面我们拿到了所有的port,但是我们并不知道这个port是哪个页面的,所以我们需要在创建SharedWorker的时候,指定每个页面的标识。

但是很遗憾,SharedWorker并没有提供这样的功能,所以我们需要自己实现。

这里我们可以使用双数组来实现,一个数组用来存储页面的标识,一个数组用来存储port

const pages = [];
const ports = [];

self.onconnect = (e) => {
   
   
    const port = e.ports[0];
    ports.push(port);

    // 获取页面标识
    const page = Math.random().toString(36).substr(2);
    port.postMessage(page);
    pages.push(page);

    // 监听消息
    port.onmessage = (e) => {
   
   
        const index = ports.indexOf(port);
        const page = pages[index];

        // 这样就可以知道是哪个页面发来的消息了
        console.log(page);
    };

};

有了上面的代码,我们就可以知道是哪个页面发来的消息了,并且也可以指定页面发送消息。

实战

上面我们已经知道了如何使用SharedWorker,那么我们就可以使用它来实现一个简单的聊天室。

这里我们使用SharedWorker来实现消息的转发,页面之间的通信使用postMessage

  • SharedWorker
const uuids = [];
const ports = [];

self.onconnect = (e) => {
   
   
    const port = e.ports[0];
    ports.push(port);

    // 获取页面标识
    const uuid = Math.random().toString(36).substr(2);
    uuids.push(uuid);
    port.postMessage({
   
   
        type: 'connect',
        uuid: uuid
    });

    // 监听消息
    port.onmessage = (e) => {
   
   
        const index = ports.indexOf(port);
        const uuid = uuids[index];

        // 群发消息
        broadcast({
   
   
            type: 'message',
            sender: uuid,
            message: e.data.message
        });
    };
};

// 群发消息
const broadcast = (data) => {
   
   
    ports.forEach((port) => {
   
   
        port.postMessage(data);
    });
}
  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <style>
        .chat {
    
    
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
        }

        #messages {
    
    
            height: 500px;
            width: 300px;
            overflow: auto;
            border: #3498db 1px solid;
            padding: 0 10px;
        }

        .chat__messages {
    
    
            flex: 1;
            overflow-y: auto;
            margin: 4px 0;
        }

        .sender {
    
    
            font-size: 12px;
            margin: 0;
            padding: 0;
        }
        .sender:after {
    
    
            content: ' :';
        }

        .message {
    
    
            width: auto;
            max-width: 300px;
            background: #3498db;
            padding: 10px;
            border-radius: 10px;
            margin: 10px 0;
            font-size: 16px;
            color: #fff;
        }
    </style>
</head>
<body>

<div class="chat">
    <div id="messages">

    </div>

    <div class="chat__input">
        <input type="text" id="message">
        <button id="send">发送</button>
    </div>
</div>

<script>

    let uuid = ''
    const sharedWorker = new SharedWorker('sharedWorker.js');

    sharedWorker.port.onmessage = (e) => {
    
    
        if (e.data.type === 'connect') {
    
    
            uuid = e.data.uuid;
            alert('连接成功')
            return;
        }

        const messageDom = `
         <div class="chat__messages">
            <p class="sender" ${uuid === e.data.sender ? 'style="color: #165DFF"' : ""}>${e.data.sender}</p>
            <p class="message">${
      
      e.data.message}</p>
         </div>
        `
        document.getElementById('messages').insertAdjacentHTML('beforeend', messageDom);

        // 滚动到底部
        document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;
    };

    document.getElementById('send').addEventListener('click', () => {
    
    
        const message = document.getElementById('message').value;
        if (!message) {
    
    
            alert('请输入内容')
            return;
        }

        const data = {
    
    
            type: 'message',
            message: message,
        }
        sharedWorker.port.postMessage(data);
        document.getElementById('message').value = '';
    });
</script>
</body>
</html>

上面的代码我们实现了一个简单的聊天室,我们可以在多个页面打开聊天室,然后在其中一个页面发送消息,其他页面都会收到消息。

上面的代码中还有一个uuid,=还可以基于这么来实现私聊,比如我们可以在页面中添加一个select,然后选择一个用户,然后在发送消息的时候,将uuid传递过去,然后在SharedWorker中判断,如果是私聊,就只发送给指定的用户。

这一块就留给感兴趣的同学自己实现了。

SharedWorker 的关闭

上面我们实现了一个简单的聊天室,但是我们发现,当我们关闭页面的时候,SharedWorker并没有关闭,这是因为SharedWorker是一个长连接,当我们关闭页面的时候,SharedWorker并没有关闭,所以我们需要在页面关闭的时候,手动关闭SharedWorker

// 关闭页面的时候,关闭 SharedWorker
window.addEventListener('beforeunload', () => {
   
   
    sharedWorker.port.postMessage({
   
   
        type: 'close',
        uuid: uuid
    });
});

// SharedWorker.js
/** 省略其他代码 **/
onconnect = (e) => {
   
   
    const port = e.ports[0];

    port.onmessage = (e) => {
   
   
        if (e.data.type === 'close') {
   
   
            const index = ports.indexOf(port);
            ports.splice(index, 1);
            return;
        }
    };
};

上面的代码中,我们在页面关闭的时候,发送一个close的消息,然后在SharedWorker中,将portports中移除,这样就可以关闭SharedWorker了。

SharedWorker 的生命周期

SharedWorker的生命周期没有那么复杂,不像ServiceWorker有那么多的过程,它只有一条规则,就是当所有创建SharedWorker的页面关闭之后,那么SharedWorker的生命就走到了尽头,否则它就会一直常驻。

SharedWorker 还可以做什么?

聊天室只是一个小玩具,我们还可以做很多事情,我们最常见的就是跨页面登录,例如GitHub,我们在一个页面登录,然后在其他页面就可以获取到登录信息,这就是SharedWorker的一个应用场景。

还有一个应用场景就是,我们可以在一个页面中,监听一个WebSocket,然后在其他页面中,发送消息,这样就可以实现跨页面的WebSocket

其实任何跨页面的通信,都可以使用SharedWorker来实现,但是SharedWorker的缺点也很明显,就是不支持IE,所以如果你的项目需要兼容IE,那么就不要使用SharedWorker了。

优点很明确,就是可以跨页面通信,缺点也很明显,就是浏览器兼容确实有点捉急。

image.png

总结

本文主要介绍了SharedWorker的基本使用,并使用SharedWorker实现了一个简单的聊天室。

SharedWorker的使用还是比较简单的,不同于Worker那么单纯,SharedWorker是一个长连接,所以我们可以在多个页面中,共享一个SharedWorker,这样就可以实现跨页面通信了。

不同于ServiceWorker有那么多的限制,SharedWorker除了兼容性其实功能非常强大,且在实际项目中的应用场景也更加广泛。

历史章节和预告

目录
相关文章
|
Web App开发 运维 安全
SSL/TLS证书1年有效期新规已至,被“证书过期”支配的恐惧又增加了!
9月1日起,两年期公共SSL/TLS证书正式告别了行业,在三大浏览器(Apple Safari、 Google Chrome、Mozilla Firefox)的推动下,SSL/TLS证书最长有效期变更为13个月,同时,全球各大证书权威签发机构已停止签发有效期超过1年(398天)的SSL证书。
SSL/TLS证书1年有效期新规已至,被“证书过期”支配的恐惧又增加了!
|
XML 域名解析 JSON
【RESTful】RESTful API 接口设计规范 | 示例
【RESTful】RESTful API 接口设计规范 | 示例
12312 0
【RESTful】RESTful API 接口设计规范 | 示例
|
9月前
|
前端开发
Promise.allSettled()和Promise.all()在处理错误时的差异是什么?
Promise.allSettled()和Promise.all()在处理错误时的差异是什么?
267 74
|
10月前
|
网络协议 应用服务中间件 网络安全
免费泛域名https证书教程—无限免费续签
随着互联网安全意识提升,越来越多网站采用HTTPS协议。本文介绍如何通过JoySSL轻松获取并实现免费泛域名SSL证书的无限续签。JoySSL提供永久免费通配符SSL证书,支持无限制域名申请及自动续签,全中文界面适合国内用户。教程涵盖注册账号、选择证书类型、验证域名所有权、下载与安装证书以及设置自动续签等步骤,帮助网站简化SSL证书管理流程,确保长期安全性。
|
12月前
|
人工智能 JavaScript 数据可视化
Cursor、v0 和 Bolt.new:当今 AI 编程工具的全面解析与对比
本文深入解析了 Cursor AI、v0 和 Bolt.new 三大 AI 编程工具的特点与应用场景。Cursor 适合日常编码与团队协作,v0 专注于 UI 原型设计,Bolt.new 擅长全栈原型开发。各工具在功能上互为补充,开发者可根据需求灵活选择,以提升工作效率。
4884 1
|
运维 数据安全/隐私保护 Docker
Docker自建仓库之Docker Registry部署实战
关于如何使用Docker Registry镜像搭建本地私有Docker仓库的实战教程,包括了下载镜像、创建授权目录和用户名密码、启动Registry容器、验证端口和容器、测试登录仓库、上传和下载镜像的详细步骤。
3081 5
|
JavaScript 前端开发 算法
深入理解虚拟DOM:原理、优势与实践
深入理解虚拟DOM:原理、优势与实践
1358 0
|
开发框架 开发工具 Java
从零到高手:一文教你快速上手Vaadin,打造首个炫酷Web应用,开启编程新世界的大门
【8月更文挑战第31天】Vaadin是一款基于Java的Web应用开发框架,以其丰富的组件库、简洁的API及对现代Web标准的支持而广受开发者欢迎。本文将指导你从零开始快速搭建一个简单的Vaadin应用。首先确保已安装JDK 11及以上版本和IDE(如IntelliJ IDEA或Eclipse)。接着使用Spring Initializr创建新项目,并选中“Spring Web”和“Vaadin”依赖。创建项目后,编写主应用类和主页类,实现基本页面功能。最后配置启动页面并运行应用。通过这些步骤,你将成功搭建一个基础Vaadin应用,开启探索更多特性的旅程。
1631 0
|
Shell 容器
Ceph Reef(18.2.X)访问ceph集群的方式及管理员节点配置案例
这篇文章是关于Ceph Reef(18.2.X)版本中访问ceph集群的方式和管理员节点配置的案例,介绍了使用cephadm shell的不同方式访问集群和如何配置管理节点以方便集群管理。
500 5