# 窗口间通信

# 浏览器同源策略

同源的定义🍱

协议/主机/端口 三者相同

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
Last Updated: 11/3/2022, 10:13:52 PM