# 窗口间通信
# 浏览器同源策略
同源的定义🍱
协议/主机/端口 三者相同
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html | 同源 | 只有路径不同 |
https://store.company.com/secure.html | 失败 | 协议不同 |
http://store.company.com:81/dir/etc.html | 失败 | 端口不同 ( http:// 默认端口是 80) |
http://news.company.com/dir/other.html | 失败 | 主机不同 |
# 1、使用websocket
思路:引入第三者进行中转 。
缺点:需要引入服务端 websocket传送门
# 2、定时器+本地轮询
缺点
- 可能会产生副作用。
- cookie增加网络负担【每次都会发送到服务端】。
- FileSystem数据需要清理
- 不够及时
- 受限于同源策略
<!-- index.html -->
<style>
section { height: 100% }
section { display: flex }
iframe { flex: 1;height: 100% }
</style>
<section>
<iframe src="./page1.html"></iframe>
<iframe src="./page2.html"></iframe>
</section>
<!-- page1 -->
<body>
<h3>Page 1</h3>
<section style="margin-top:50px; text-align: center">
<input id="inputMessage" value="page 1的测试消息" />
<input type="button" value="发送消息" id="btnSend" />
<section id="messages">
<p>收到的消息:</p>
</section>
</section>
<script>
var messagesEle = document.getElementById("messages");
var messageEl = document.getElementById("inputMessage");
var btnSend = document.getElementById("btnSend");
var lastMessage = null;
setInterval(() => {
var message = sessionStorage.getItem("page2msg");
try {
if (message) {
message = JSON.parse(message);
if (!lastMessage || lastMessage.date != message.date) {
appendMessage(message);
lastMessage = message;
}
}
} catch (err) {
console.log(err);
}
}, 200);
function appendMessage(data) {
var msgEl = document.createElement("p");
msgEl.innerText = data.date + " " + data.from + ":" + data.message;
messagesEle.appendChild(msgEl);
}
btnSend.addEventListener("click", function() {
var message = messageEl.value;
sessionStorage.setItem(
"page1msg",
JSON.stringify({
date: Date.now(),
message,
from: "page 1"
})
);
});
</script>
</body>
<!-- page2 -->
<body>
<h3>Page 2</h3>
<section style="margin-top:50px; text-align: center">
<input id="inputMessage" value="page 2的测试消息" />
<input type="button" value="发送消息" id="btnSend" />
<section id="messages">
<p>收到的消息:</p>
</section>
</section>
<script>
var messagesEle = document.getElementById("messages");
var messageEl = document.getElementById("inputMessage");
var btnSend = document.getElementById("btnSend");
var lastMessage = null;
setInterval(() => {
var message = sessionStorage.getItem("page1msg");
try {
if (message) {
message = JSON.parse(message);
if (!lastMessage || lastMessage.date != message.date) {
appendMessage(message);
lastMessage = message;
}
}
} catch (err) {
console.log(err);
}
}, 200);
function appendMessage(data) {
var msgEl = document.createElement("p");
msgEl.innerText = data.date + " " + data.from + ":" + data.message;
messagesEle.appendChild(msgEl);
}
btnSend.addEventListener("click", function() {
var message = messageEl.value;
sessionStorage.setItem(
"page2msg",
JSON.stringify({
date: Date.now(),
message,
from: "page 2"
})
);
});
</script>
</body>
# 3、ostMessage
- 思路:用某种手段建立窗口中的通信。
- 优点:不受同源策略的限制
- 缺点:必须拿到对应窗口的引用
<body>
<div>
<iframe
src="./ifr.html"
id="ifr"
style="width: 600px; height: 300px"
></iframe>
</div>
index.html
<div>
<div>Message:</div>
<div id="messages"></div>
</div>
<script>
window.addEventListener("message", function (event) {
messages.innerHTML += `
<div>${event.data}</div>
`;
});
setInterval(() => {
ifr.contentWindow.postMessage(`消息来自 index.html, ${Date.now()}`);
}, 3000);
</script>
</body>
<body>
ifr.html
<div>
<div>Message:</div>
<div id="messages"></div>
</div>
<script>
window.addEventListener("message", function (event) {
messages.innerHTML += `
<div>${event.data}</div>
`;
});
setInterval(() => {
window.parent.postMessage(`消息来自 ifr.html, ${Date.now()}`);
}, 3000);
</script>
</body>
# 4、StorageEvent
localStorage/sessionStorage
当页面使用的storage被其他页面修改时会触发StorageEvent事件
缺点
- 传递数据大小有限制
- 可能需要清理工作
- 遵循同源策略
- 同窗口不能监听
<!-- index.html -->
<button id="btnSend" type="button">发送消息</button>
<div>消息:</div>
<div id="message"></div>
<script>
// 拦截,监听事件
const oriSetItem = localStorage.setItem;
Object.defineProperty(localStorage.__proto__, "setItem", {
value: function (key, value) {
var oldValue = localStorage.getItem(key);
var event = new StorageEvent("storage", {
key,
newValue: value,
oldValue,
url: document.URL,
storageArea: localStorage,
});
window.dispatchEvent(event);
oriSetItem.apply(this, arguments);
},
});
</script>
<script>
btnSend.addEventListener("click", function () {
localStorage.setItem(
"key",
JSON.stringify({
key: "key",
data: Math.random(),
})
);
});
window.addEventListener("storage", function (ev) {
message.textContent = JSON.stringify({
oldValue: ev.oldValue,
newValue: ev.newValue,
});
});
</script>
<!-- other.html -->
<body>
<div>消息:</div>
<div id="message"></div>
<script>
window.addEventListener("storage", function (ev) {
message.textContent = JSON.stringify({
oldValue: ev.oldValue,
newValue: ev.newValue,
});
});
</script>
</body>
# 5、Broadcast Channel
TIP
允许同源的不同浏览器窗口,Tab页,iframe以及iframe下的不同文档之间相互通信
缺点
同源策略限制
<!-- index.html -->
<section>
<iframe src="./page1.html"></iframe>
<iframe src="./page2.html"></iframe>
</section>
<!-- page1.html -->
<script>
var channel = new BroadcastChannel("channel-BroadcastChannel");
channel.postMessage('page1 的消息')
</script>
<!-- page2.html -->
<script>
var channel = new BroadcastChannel("channel-BroadcastChannel");
channel.addEventListener('message',function(ev){
console.log(ev.data)
})
</script>
# 6、MessageChannel
Channel Messaging API 的MessageChannel 接口允许我们创建一个新的消息通道,并通过它的两个MessagePort 属性发送数据。
var channel = new MessageChannel();
var para = document.querySelector('p');
var ifr = document.querySelector('iframe');
var otherWindow = ifr.contentWindow;
ifr.addEventListener("load", iframeLoaded, false);
function iframeLoaded() {
otherWindow.postMessage('Hello from the main page!', '*', [channel.port2]);
}
channel.port1.onmessage = handleMessage;
function handleMessage(e) {
para.innerHTML = e.data;
}
# 7、sharedWorker
缺点
兼容性、同源策略
// worker.js
var portList = [];
onconnect = function (e) {
var port = e.ports[0];
ensurePorts(port);
port.onmessage = function (e) {
var data = e.data;
disptach(port, data);
};
port.start();
};
function ensurePorts(port) {
if (portList.indexOf(port) < 0) {
portList.push(port);
}
}
function disptach(selfPort, data) {
portList
.filter((port) => selfPort !== port)
.forEach((port) => port.postMessage(data));
}
<!-- index.html -->
<section>
<iframe src="./page1.html"></iframe>
<iframe src="./page2.html"></iframe>
</section>
<!-- Page 1 -->
<body>
<h3>Page 1</h3>
<section style="margin-top: 50px; text-align: center">
<input id="inputMessage" value="page 1的测试消息" />
<input type="button" value="发送消息" id="btnSend" />
<section id="messages">
<p>收到的消息:</p>
</section>
</section>
<script src="./worker.js"></script>
<script>
var messagesEle = document.getElementById("messages");
var messageEl = document.getElementById("inputMessage");
var btnSend = document.getElementById("btnSend");
//
if (!window.SharedWorker) {
alert("浏览器不支持SharedWorkder!");
} else {
var myWorker = new SharedWorker("./worker.js");
myWorker.port.onmessage = function (e) {
var msgEl = document.createElement("p");
var data = e.data;
msgEl.innerText = data.date + " " + data.from + ":" + data.message;
messagesEle.appendChild(msgEl);
};
btnSend.addEventListener("click", function () {
var message = messageEl.value;
myWorker.port.postMessage({
date: new Date().toLocaleString(),
message,
from: "page 1",
});
});
myWorker.port.start();
}
</script>
</body>
<!-- page 2.html -->
<body>
<h3>Page 2</h3>
<section style="margin-top: 50px; text-align: center">
<input id="inputMessage" value="page 1的测试消息" />
<input type="button" value="发送消息" id="btnSend" />
<section id="messages">
<p>收到的消息:</p>
</section>
</section>
<script src="./worker.js"></script>
<script>
var messagesEle = document.getElementById("messages");
var messageEl = document.getElementById("inputMessage");
var btnSend = document.getElementById("btnSend");
if (!window.SharedWorker) {
alert("浏览器不支持SharedWorkder!");
} else {
var myWorker = new SharedWorker("./worker.js");
myWorker.port.onmessage = function (e) {
var msgEl = document.createElement("p");
var data = e.data;
msgEl.innerText = data.date + " " + data.from + ":" + data.message;
messagesEle.appendChild(msgEl);
};
btnSend.addEventListener("click", function () {
var message = messageEl.value;
var message = messageEl.value;
myWorker.port.postMessage({
date: new Date().toLocaleString(),
message,
from: "page 2",
});
});
myWorker.port.start();
}
</script>
</body>
# 总结
方法 | 是否需要有强关联 | 遵循同源策略 | web worker可用 |
---|---|---|---|
websocket | ✅ | ||
定时器+客户端存储 | ✅ | indexedDb | |
postMessage | ✅ | 自己的postMessage | |
storageEvent | ✅ | ||
Broadcast Channel | ✅ | ✅ | |
MessageChannel | ✅ | ✅ | |
SharedWorker | ✅ | ✅ |