解锁浏览器内置API,助力跨标签/跨页面数据通信
1 BrodcastChanner
概念
BroadcastChannel
接口表示给定源的任何浏览上下文都可以订阅的命名频道。它允许同源的不同浏览器窗口、标签页、frame 或者 iframe 下的不同文档之间相互通信。消息通过message事件进行广播,该事件在侦听该频道的所有BroadcastChannel
对象上触发,发送消息的对象除外。
何为源?
- 由用于访问它的URL的方案(协议)、主机名(域名)和端口定义。只有当协议、主机和端口都匹配时,两个对象才具有相同的源。
何为浏览上下文?
- 浏览器(browser)展示文档的环境。在现代浏览器中,通常是一个标签页(tab),也可能是一个窗体(window)或只是页面的一部分。
构成
- 构造函数:BroadcastChannel
- 实例属性:name,频道名称
- 实例方法:
- 发送消息:postMessage
- 关闭频道对象:close
- 事件:
- 收到消息时触发:message,也可以使用onmessage属性访问。
使用
1.创建一个链接到命名频道的对象
const broad = new BroadcastChannel('moment')
2.绑定事件,以便接收消息
broad.onmessage = function (e) {
const { value } = e.data
console.log('value::',value)
}
3.按需进行消息发送
broad.postMessage({
value: 'Hi,my baby'
})
代码落地
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>index</title>
<link rel="icon" type="image/png" href="../images/applogo.png" />
<link rel="stylesheet" href="../css/common.css" />
</head>
<body>
<div class="user-info">
<img class="avatar" src="../images/2.jpg" alt="" />
<p class="u-name">姓名:昔冰</p>
<p class="u- signature">个签:积善者必有余庆</p>
</div>
<ul class="avatar-list">
<li class="avatar-item" data-index="0"></li>
<li class="avatar-item" data-index="1"></li>
<li class="avatar-item" data-index="2"></li>
</ul>
</body>
<script>
const img = document.querySelector('.avatar')
const avatarList = ['../images/1.jpg', '../images/2.jpg', '../images/3.png']
const ul = document.querySelector('.avatar-list')
ul.addEventListener('click', (e) => {
// console.log(e.target.dataset.index)
const index = e.target.dataset.index
const imgUrl = avatarList[index]
useBroadSendMsg(imgUrl)
img.src = imgUrl
// console.log(imgUrl)
})
const broad = new BroadcastChannel('moment')
broad.onmessage = function (e) {
const { value } = e.data
img.src = value
}
const useBroadSendMsg = (data) => {
setTimeout(() => {
broad.postMessage({
value: data
})
}, 500)
}
</script>
</html>
<!-- other.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>other</title>
<link rel="icon" type="image/png" href="../images/applogo.png" />
<link rel="stylesheet" href="../css/common.css" />
</head>
<body>
<div class="user-info">
<img class="avatar" src="../images/2.jpg" alt="" />
<p class="u-name">姓名:昔冰</p>
<p class="u- signature">个签:积善者必有余庆</p>
</div>
<ul class="avatar-list">
<li class="avatar-item" data-index="0"></li>
<li class="avatar-item" data-index="1"></li>
<li class="avatar-item" data-index="2"></li>
</ul>
</body>
<script>
const img = document.querySelector('.avatar')
const avatarList = ['../images/1.jpg', '../images/2.jpg', '../images/3.png']
const ul = document.querySelector('.avatar-list')
ul.addEventListener('click', (e) => {
// console.log(e.target.dataset.index)
const index = e.target.dataset.index
const imgUrl = avatarList[index]
useBroadSendMsg(imgUrl)
img.src = imgUrl
// console.log(imgUrl)
})
const useBroadSendMsg = (data) => {
setTimeout(() => {
broad.postMessage({
value: data
})
}, 500)
}
const broad = new BroadcastChannel('moment')
broad.onmessage = function (e) {
const { value } = e.data
img.src = value
}
</script>
</html>
/* common.css */
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
ul {
list-style: none;
}
.avatar-list {
margin: 20px auto;
width: 300px;
display: flex;
justify-content: space-around;
}
.user-info {
text-align: center;
}
.avatar {
width: 100px;
height: 100px;
border-radius: 10px;
}
.avatar-item {
width: 80px;
height: 80px;
}
.avatar-item:hover {
cursor: pointer;
}
.avatar-item:nth-child(1) {
background-image: url('../images/1.jpg');
background-size: 100% 100%;
}
.avatar-item:nth-child(2) {
background-image: url('../images/2.jpg');
background-size: 100% 100%;
}
.avatar-item:nth-child(3) {
background-image: url('../images/3.png');
background-size: 100% 100%;
}
2 ServiceWorker
概念
ServiceWorker
接口提供了对 service worker 的引用。各个浏览上下文(例如页面、worker 等)可以与相同的 service worker 相关联,每个浏览上下文都可以通过唯一的ServiceWorker
对象访问。
Service worker 运行在 worker 上下文:因此它无法访问 DOM,相对于驱动应用的主 JavaScript 线程,它运行在其他线程中,所以不会造成阻塞。
何为worker上下文?
- 在浏览器环境中,Web Worker 是一种可以在浏览器后台运行脚本的机制,它允许在主线程之外创建独立的线程来执行 JavaScript 代码。Service Worker 就是基于 Web Worker 技术实现的,它运行在一个特殊的 worker 上下文环境中。
何为无法访问DOM?
- 由于 Service Worker 运行在独立的 worker 上下文中,与主页面的 DOM 环境是隔离的。
- 这意味着 Service Worker 不能直接对页面上的元素进行修改,比如改变文本内容、修改样式等。如果需要更新页面内容,Service Worker 可以通过向主页面发送消息,由主页面的 JavaScript 代码来完成 DOM 操作。
运行在其他线程中?
- 在浏览器中,主 JavaScript 线程负责处理用户交互、页面渲染和执行主页面的 JavaScript 代码。如果主线程执行了耗时的操作,如大量的计算或长时间的网络请求,会导致页面卡顿,用户体验变差。
- 而 Service Worker 运行在独立于主 JavaScript 线程的其他线程中,它有自己的执行栈和事件循环。这意味着 Service Worker 中的代码执行不会阻塞主页面的渲染和交互。
既然是独立出来的,那如何操作呢?
self
- 在 Service Worker 中,
self
是一个全局对象,它类似于主页面中的window
对象。self
代表当前的 Service Worker 实例本身,通过它可以访问 Service Worker 的各种方法和属性,也可以监听 Service Worker 生命周期中的各种事件。
self.clients
self.clients
是一个Clients
对象,它代表了当前与 Service Worker 关联的所有客户端(通常是浏览器的标签页或窗口)。通过self.clients
可以对这些客户端进行管理和操作,比如获取客户端列表、向客户端发送消息等。- 可以通过
client.postMessage()
方法向指定的客户端发送消息。
代码落地
// service-worker.js
// 监听消息事件
self.addEventListener('message', function (event) {
// 获取发送消息的客户端 ID
const senderId = event.source.id
// 获取所有受控的客户端
self.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
// 避免将消息发送回发送者
if (client.id !== senderId) {
// 向其他客户端发送消息
client.postMessage(event.data)
}
})
})
})
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>index</title>
<link rel="icon" type="image/png" href="../images/applogo.png" />
<link rel="stylesheet" href="../css/common.css" />
</head>
<body>
<div class="user-info">
<img class="avatar" src="../images/2.jpg" alt="" />
<p class="u-name">姓名:昔冰</p>
<p class="u- signature">个签:积善者必有余庆</p>
</div>
<ul class="avatar-list">
<li class="avatar-item" data-index="0"></li>
<li class="avatar-item" data-index="1"></li>
<li class="avatar-item" data-index="2"></li>
</ul>
</body>
<script>
const img = document.querySelector('.avatar')
const avatarList = ['../images/1.jpg', '../images/2.jpg', '../images/3.png']
const ul = document.querySelector('.avatar-list')
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker
.register('./service-worker.js')
.then(function (registration) {
console.log('Service Worker registered successfully:', registration)
// 监听来自Service Worker 的消息
navigator.serviceWorker.addEventListener('message', function (event) {
const { type, value } = event.data
if (type === 'change-avatar') {
img.src = value
}
})
// DOM操作
ul.addEventListener('click', (e) => {
// console.log(e.target.dataset.index)
const li = e.target
if (li.tagName === 'LI') {
const index = li.dataset.index
const imgUrl = avatarList[index]
// 发送消息
navigator.serviceWorker.controller.postMessage({
type: 'change-avatar',
value: imgUrl
})
img.src = imgUrl
}
})
})
.catch(function (error) {
console.error('Service Worker registration failed:', error)
})
})
}
</script>
</html>
<!-- other.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>other</title>
<link rel="icon" type="image/png" href="../images/applogo.png" />
<link rel="stylesheet" href="../css/common.css" />
</head>
<body>
<div class="user-info">
<img class="avatar" src="../images/2.jpg" alt="" />
<p class="u-name">姓名:昔冰</p>
<p class="u- signature">个签:积善者必有余庆</p>
</div>
<ul class="avatar-list">
<li class="avatar-item" data-index="0"></li>
<li class="avatar-item" data-index="1"></li>
<li class="avatar-item" data-index="2"></li>
</ul>
</body>
<script>
const img = document.querySelector('.avatar')
const avatarList = ['../images/1.jpg', '../images/2.jpg', '../images/3.png']
const ul = document.querySelector('.avatar-list')
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker
.register('./service-worker.js')
.then(function (registration) {
console.log('Service Worker registered successfully:', registration)
// 监听来自 Service Worker 的消息
navigator.serviceWorker.addEventListener('message', function (event) {
const { type, value } = event.data
if (type === 'change-avatar') {
img.src = value
}
})
// 发送消息的按钮点击事件
// DOM操作
ul.addEventListener('click', (e) => {
// console.log(e.target.dataset.index)
const li = e.target
if (li.tagName === 'LI') {
const index = li.dataset.index
const imgUrl = avatarList[index]
// 发送消息
navigator.serviceWorker.controller.postMessage({
type: 'change-avatar',
value: imgUrl
})
img.src = imgUrl
}
})
})
.catch(function (error) {
console.error('Service Worker registration failed:', error)
})
})
}
</script>
</html>
3 postMessage
概念
**window.postMessage()**方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数Document.domain设置为相同的值) 时,这两个脚本才能相互通信。
从广义上讲,一个窗口可以获得对另一个窗口的引用(比如
targetWindow = window.opener
),然后在窗口上调用targetWindow.postMessage()
方法分发一个MessageEvent消息。接收消息的窗口可以根据需要自由处理此事件。
怎么拿到另一个窗口的引用?
其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。
代码落地
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>index</title>
<link rel="icon" type="image/png" href="../images/applogo.png" />
<link rel="stylesheet" href="../css/common.css" />
<link rel="stylesheet" href="./c-post.css" />
</head>
<body>
<div class="user-info">
<img class="avatar" src="../images/2.jpg" alt="" />
<p class="u-name">姓名:昔冰</p>
<p class="u- signature">个签:积善者必有余庆</p>
</div>
<ul class="avatar-list">
<li class="avatar-item" data-index="0"></li>
<li class="avatar-item" data-index="1"></li>
<li class="avatar-item" data-index="2"></li>
</ul>
<div class="edit-box">
<button class="btn1">打开other页面</button>
</div>
</body>
<script>
const img = document.querySelector('.avatar')
const avatarList = ['../images/1.jpg', '../images/2.jpg', '../images/3.png']
const ul = document.querySelector('.avatar-list')
let targetWindow = null
const btn1 = document.querySelector('.btn1')
const btn2 = document.querySelector('.btn2')
btn1.onclick = () => {
targetWindow = window.open('./other.html')
}
ul.addEventListener('click', (e) => {
const li = e.target
if (li.tagName === 'LI') {
const index = li.dataset.index
const imgUrl = avatarList[index]
targetWindow.postMessage({ value: imgUrl }, 'http://127.0.0.1:5500')
img.src = imgUrl
}
})
</script>
</html>
<!-- other.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>other</title>
<link rel="icon" type="image/png" href="../images/applogo.png" />
<link rel="stylesheet" href="../css/common.css" />
<link rel="stylesheet" href="./c-post.css" />
</head>
<body>
<div class="user-info">
<img class="avatar" src="../images/2.jpg" alt="" />
<p class="u-name">姓名:昔冰</p>
<p class="u- signature">个签:积善者必有余庆</p>
</div>
<ul class="avatar-list">
<li class="avatar-item" data-index="0"></li>
<li class="avatar-item" data-index="1"></li>
<li class="avatar-item" data-index="2"></li>
</ul>
</body>
<script>
const img = document.querySelector('.avatar')
const avatarList = ['../images/1.jpg', '../images/2.jpg', '../images/3.png']
const ul = document.querySelector('.avatar-list')
const btn = document.querySelector('button')
window.addEventListener('message', function (event) {
if (event.origin == 'http://127.0.0.1:5500') {
const { value } = event.data
img.src = value
}
})
</script>
</html>
4 Storage
当存储区域(localStorage 或 sessionStorage)被修改时,将触发 storage 事件。
对于全局localstorage,知道的是:
- 键值对形式
- 存储只能存储字符串内容,复杂类型数据需要使用JSON做序列化处理;
- 不会随页面刷新而丢失
使用
借助storage事件——在当前修改的这个页面不会触发,也就是行为发生的页面的storage事件不会触发。
三个常用字段:
- key:"键值对"
- newValue:"新值"
- oldValue:"旧值"
代码落地
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>index</title>
<link rel="icon" type="image/png" href="../images/applogo.png" />
<link rel="stylesheet" href="../css/common.css" />
</head>
<body>
<div class="user-info">
<img class="avatar" src="../images/2.jpg" alt="" />
<p class="u-name">姓名:昔冰</p>
<p class="u- signature">个签:积善者必有余庆</p>
</div>
<ul class="avatar-list">
<li class="avatar-item" data-index="0"></li>
<li class="avatar-item" data-index="1"></li>
<li class="avatar-item" data-index="2"></li>
</ul>
</body>
<script>
const img = document.querySelector('.avatar')
const avatarList = ['../images/1.jpg', '../images/2.jpg', '../images/3.png']
const ul = document.querySelector('.avatar-list')
ul.addEventListener('click', (e) => {
const li = e.target
if (li.tagName === 'LI') {
const index = li.dataset.index
const imgUrl = avatarList[index]
// 存储数据
localStorage.setItem('avatar', imgUrl)
img.src = imgUrl
}
})
window.addEventListener('storage', (e) => {
const newUrl = e.newValue
img.src = newUrl
})
</script>
</html>
<!-- other.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>other</title>
<link rel="icon" type="image/png" href="../images/applogo.png" />
<link rel="stylesheet" href="../css/common.css" />
</head>
<body>
<div class="user-info">
<img class="avatar" src="../images/2.jpg" alt="" />
<p class="u-name">姓名:昔冰</p>
<p class="u- signature">个签:积善者必有余庆</p>
</div>
<ul class="avatar-list">
<li class="avatar-item" data-index="0"></li>
<li class="avatar-item" data-index="1"></li>
<li class="avatar-item" data-index="2"></li>
</ul>
</body>
<script>
const img = document.querySelector('.avatar')
const avatarList = ['../images/1.jpg', '../images/2.jpg', '../images/3.png']
const ul = document.querySelector('.avatar-list')
ul.addEventListener('click', (e) => {
const li = e.target
if (li.tagName === 'LI') {
const index = li.dataset.index
const imgUrl = avatarList[index]
// 存储数据
localStorage.setItem('avatar', imgUrl)
img.src = imgUrl
}
})
window.addEventListener('storage', (e) => {
const newUrl = e.newValue
img.src = newUrl
})
</script>
</html>