文章目录
Iframe内嵌相互传递
使用window.postMessage()
的这个html5特性去跨域传递数据,不受跨域限制。
父层:
sendMes(){ // 向iframe发送
let iframdom = this.$refs.iframdom // 拿到iframe的dom
let _window = iframdom.contentWindow // 拿到iframe的window对象
let _document = iframdom.contentDocument // 拿到iframe的document对象
_window.postMessage({type: 'sendMes', text: '内容'}, '*')
// 注意点:1 第一个参数传内容用对象装,因为webpack会默认主动触发一次,
// 即使sendMes没执行,这样被第一次触发的时候我们能通过对象的内容去区分。
// 2 第二个参数写传入的地址是什么,如果不限制地址写*即可
}
window.addEventListener("message", (e) => { // 接收子层
// e.data及postMessage的第一个参数,e.origin及postMessage的第二个参数
})
子层:
window.addEventListener("message", (e) => { // 接收父层
// e.data及postMessage的第一个参数,e.origin及postMessage的第二个参数
})
sendOut(){ // 向父层发送
window.top.postMessage({type: 'sendMes', text: '内容'}, '*')
}
个人对这个api的看法:
- 能够支持跨域,这样不同项目的网站都能相互通信了。
- 当传输的内容多了,整个工程到处都是postMessage,到处监听,会非常的混乱(建议监听统一放到类似App.vue的组文件中)。
- 父层与子层需要定好规则,例如你的项目是子层,就要去问负责开发父层网站的开发人员,怎么按照他们的规范去传递数据和获取回数据
这里推荐篇基础使用文章:【记录postMessage的详细使用】
再推荐篇补充的:【终于搞懂了 Iframe (跨窗口通信)】
window.postMessage()
window.postMessage()
不仅用于iframe,还可以主动打开页面让后建立信息传递通道,例如页面A
let opener = null; // 保存打开窗口的引用
popBtn.onclick = function () {
opener = window.open("B.html", "123", "height=400,width=400,top=10,resizable=yes");
}
btn.onclick = function () {
let data = {
value: content.value
}
// data 代表的是发送是数据,origin 用来限制访问来源,也可以用 * 代替
opener.postMessage(data, "*");
}
被打开的页面B:
window.addEventListener('message', function (e) {
console.log(e.data);
}, false);//事件监听
想这种方式我更愿意用url带参数去区分
BroadcastChannel同标签页数据传递
这个api适合同一个浏览器中,访问多个标签页,标签页之间的通信:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
</style>
</head>
<body>
<button id="btn">点击</button>
<script>
let btnDom = btn
const channel = new BroadcastChannel('card')
btnDom.onclick = function () {
channel.postMessage({
a: 1
})
}
channel.onmessage = (e) => {
console.log('监听到信息', e.data);
}
</script>
</body>
</html>
当a标签页点击了按钮,b标签页就能够接收到数据。兼容性也很好。
当然在框架中使用有些注意事项:
用添加监听器的方式去监听channel的信息传递,这样好销毁内存
channel.addEventListener('message', handler)
channel.removeEventListener('message', handler) // 销毁
还有一个要注意的,如果postMessage时传入的是框架里的响应式变量是会报错的,传入的时候用…处理下。
综上,最好单独写个js文件去调用BroadcastChannel:
const channel = new BroadcastChannel('demo')
export function sendMsg(type, content) {
channel.postMessage({
type,
content,
})
}
export function listenMsg(callback) {
const handler = (e) => {
callback && callback(e.data)
}
channel.addEventListener('message', handler)
return ()=>{
channel.removeEventListener('message', handler) // 让组件在使用的时候能够得到销毁函数,在生命周期钩子中销毁
}
}
localStorage中间人传递
localStorage是可以跨页签的,只要是同域的都共用一个localStorage缓存,利用这个特性可以:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
</style>
</head>
<body>
<button id="setData">存储</button>
<button id="getData">获取</button>
<script>
let setDataDom = setData
let getDataDom = getData
setDataDom.onclick = function () {
localStorage.setItem('a', 1)
}
getDataDom.onclick = function () {
console.log(localStorage.getItem('a'));
}
</script>
</body>
</html>
iframe嵌套相同域名的页面应该也是可以正常使用的,不过
- 多占用localStorage资源
- 无法做到主动接收
这里提个醒sessionStorage每个页签都是独立的
localStorage监听
想不到吧!localStorage也可以监听。
首先咱们写一个html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
</style>
</head>
<body>
<script>
localStorage.name = 'nick'
</script>
</body>
</html>
然后再写一个html文件去监听localStorage的修改事件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
window.addEventListener("storage", (e) => {
console.log(`${e.key}从${e.oldValue}修改为${e.newValue}`);
console.log(e.storageArea);
console.log(`被改变的url为${e.url}`);
}, true);
</script>
</body>
</html>
看到了吧,监听到了。
Service Worker
这个需要一个js文件作为代理服务器,例如写一个service.js:
self.addEventListener("message",async event=>{
const clients = await self.clients.matchAll(); // 利用浏览器收集所有的页面对象
clients.forEach(function(client){
client.postMessage(event.data) // 往每个页面都发送消息
});
});
然后只需要在页面中写:
// 首先注册上面写的service.js
navigator.serviceWorker.register('service.js')
.then(() => {
console.log("service worker 注册成功");
});
// navigator.serviceWorker.controller.postMessage('hello'); // 往所有页面发送消息
navigator.serviceWorker.onmessage = function ({ data }) { // 实时接收消息
console.log(data);
}
这样不同页签就能传递数据了
Shared Worker
我感觉是独立开了一个线程去处理,好几个页面都可以共享这个线程(猜测)。
首先也是要写个中间人worker.js:
var data = '';
onconnect = function (e) {
var port = e.ports[0]
port.onmessage = function (e) {
// 接收postMessage传达来的动作,是可自定义的,例如传个get,这里就可以判断,然后把存储的数据发送出去
if (e.data === 'get') {
port.postMessage(data);
data = "";
} else {
// 接收postMessage传达来的数据进行存储
data = e.data
}
}
}
发送方:
const worker = new SharedWorker('worker.js')
btn.onclick = function () {
worker.port.postMessage(content.value);
}
接收方:
var worker = new SharedWorker('worker.js');
worker.port.start()
// 这里接收worker传来的信息
worker.port.addEventListener('message', (e) => {
if(e.data){
console.log('来自worker的数据:', e.data)
}
}, false);
// 这里要轮番的向worker要信息
setInterval(function(){
// 获取和发送消息都是调用 postMessage 方法,我这里约定的是传递'get'表示获取数据。
worker.port.postMessage('get')
},1000);
缺点就是要轮番查询…
其他方式
- indexedDB,使用麻烦,一般不用考虑到
- websock,使用成本高,你想用领导不想你用
- cookie轮询,能别用cookie就别用了
很多方案其实都很优秀,只是看你的业务具体适合哪些场景