起因
接到一个需求,两个不同源的网页需要同步共享 localStorage,于是我想到了使用 iframe 来嵌入一个中间页,这个中间页就用来共享 localStorage,然后利用 postMessage 来向这两个不同源的网页转发数据。
可是后续做数据转发时遇到的问题却让我又学到了新的知识,虽然我还没找到资料学习完整,但想先提前把这些零散的内容传递给大家。
如何发现的
从这个需求入手,需要三个服务,分别是:A 站点、B 站点和一个中间页 C 站点,其中 A 和 B 都用 iframe 把 C 嵌入进来。
A 发送设置数据的消息给 C,大致内容如下:
// 假设点击某个按钮执行了这个逻辑,put 表示要写数据
const iframePage = document.getElementById('iframe-page');
iframePage.contentWindow.postMessage(
{
type: 'put',
value: 'hello world',
},
'*'
);
// 监听消息
window.addEventListener('message', function (e) {
console.log('A', e)
})
B 用来发送清空数据的消息,大致内容如下:
// 假设点击某个按钮执行这个逻辑,clear 表示要删除数据
const iframePage = document.getElementById('iframe-page');
iframePage.contentWindow.postMessage(
{
type: 'clear',
},
'*'
);
// 监听消息
window.addEventListener('message', function (e) {
console.log('B', e)
})
C 接到消息后,操作 localStorage,大致内容如下:
window.addEventListener('message', function (e) {
if (e.data.type === 'put') {
window.localStorage.setItem('mydata', e.data.value);
}
if (e.data.type === 'clear') {
window.localStorage.removeItem('mydata');
}
});
// 在这里我需要监听 localStorage 的改动并向父窗口发送消息通知,以便父窗口能够及时做处理
window.addEventListener('storage', function (e) {
console.log('storage');
window.parent.postMessage(
{
type: 'response',
value: 'done',
},
'*'
);
});
这时,我点 A 的按钮,发现只有 B 会收到回应消息,点 B 的按钮,只有 A 收到回应消息
我有点莫名其妙,消息不应该是广播给所有父窗口的吗?
然后查文档才看到有这么个提示:
该事件不在导致数据变化的当前页面触发(如果浏览器同时打开一个域名下面的多个页面,当其中的一个页面改变 sessionStorage 或 localStorage 的数据时,其他所有页面的 storage 事件会被触发,而原始页面并不触发 storage 事件)
这波操作可以啊!
可是,我是利用 A 发的消息让 C 改的,数据变化的页面是 C,为啥 A 却收不到呢?现在没查到相关资料,还搞不懂。
接下来越发让我不明白的事情又来了,我以为 storage 的这种表现不稳定,有点不受自己控制的感觉,于是又想到了重写 localStorage 的方法来触发我想要的事件,于是又在 C 站点加上了以下代码:
// 重写 setItem 方法
var orignalSetItem = localStorage.setItem;
localStorage.setItem = function (key, newValue) {
var setItemEvent = new Event('setItemEvent');
setItemEvent.newValue = newValue;
window.dispatchEvent(setItemEvent);
orignalSetItem.apply(this, arguments);
};
// 重写 removeItem 方法
var orignalRemoveItem = localStorage.removeItem;
localStorage.removeItem = function () {
var removeEvent = new Event('removeEvent');
window.dispatchEvent(removeEvent);
orignalRemoveItem.apply(this, arguments);
};
// 监听自定义事件
window.addEventListener('setItemEvent', function (e) {
console.log('setItemEvent');
window.parent.postMessage(
{
type: 'response',
value: '',
},
'*'
);
});
// 监听自定义事件
window.addEventListener('removeEvent', function (e) {
console.log('removeEvent');
window.parent.postMessage(
{
type: 'response',
value: '',
},
'*'
);
});
这时,我点 A 的按钮,发现只有 A 自己能收到回应消息,点 B 的按钮只有 B 自己能收到回应消息。
我突然有种天旋地转的感觉,又好像发现了点什么东西,无奈文档上又没有多余的信息。
我想 storage 事件和自定义事件等等这些事件在 postMessage 中会不会有一种特别的流向机制?
按我的想法,执行 setItem 触发了自定义事件后,window.parent.postMessage
向父窗口发消息时,所有窗口都能收到才对呀,为啥自己发的只有自己能收,而 storage 事件却是自己发的只有别人能收,postMessage 到底是一种什么样的机制呢?
postMessage 这个问题,从表现上来看应该只有 storage 事件是一个特例(A 发消息,C 通过它会发送给其它页面),其它情况下谁发给 C,C 就回应给谁。
因为页面里面的事件类型太多没心思一个个捋,看 HTML 标准的 postMessage 也没有找到,所以就此结案。😬
可以利用 storage 这个特性,使用 localStorage 做两个页面间的数据同步,是解决不同域下 localStorage 共享问题的一个相对简单的方案。
「如果有大佬知道这个问题的个中原理,还望指点」