nginx

JSONP

CORS

proxy 客户端代理

web socket

postMessage

  1. 概念

    • window.postMessage() 方法允许来自一个文档的脚本可以传递文本消息到另一个文档里的脚本,而不用管是否跨域,一个文档里的脚本还是不能调用在其他文档里方法和读取属性,但他们可以用这种消息传递技术来实现安全的通信;
    • 这项技术称为“跨文档消息传递”,又称为“窗口间消息传递”或者“跨域消息传递”;
    • postMessage() 方法,该方法允许有限的通信 —— 通过异步消息传递的方式 —— 在来自不同源的脚本之间;
  2. 语法:postMessage(message, targetOrigin, [transfer])

    • message:要发送的数据,它将会被结构化克隆算法序列化,所以无需自己序列化(部分低版本浏览器只支持字符串,所以发送的数据最好用JSON.stringify() 序列化);
    • targetOrigin:通过 targetOrigin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串“*”(表示无限制)或者一个 URI(如果要指定和当前窗口同源的话可设置为"/")。在发送消息的时候,如果目标窗口的协议、主机地址或端口号这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会发送;
  3. 使用场景

    • 页面和其打开的新窗口的数据传递;
    • 页面与嵌套的 iframe 消息传递;
    • 多窗口之间消息传递;
  4. 安全问题

    • 如果希望从其他网站接收 message,请不要为 message 事件添加任何事件监听器;
    • 如果希望从其他网站接收 message,请始终使用 origin 和 source 属性验证发件人的身份;
    • 当使用 postMessage 将数据发送到其他窗口时,始终指定精确的目标 origin,而不是 *;
  5. 代码实现

    HTML
    HTML
    <!-- a.html http://127.0.0.1:5500-->
    <body>
        <iframe id="iframe" src="http://127.0.0.1:5501/b.html"></iframe>
    </body>
    <script>
        var iframe = document.getElementById('iframe');
        iframe.onload = function () {
            // 向 domain2 发送跨域数据
            iframe.contentWindow.postMessage('a.html 发出的消息', 'http://127.0.0.1:5501');
        };
        // 接受 domain2 返回数据
        window.addEventListener('message', (e) => {
            console.log(e.data);
        }, false);
    </script>
    
    <!-- b.html http://127.0.0.1:5501-->
    <script>
        // 接收 domain1 的数据
        window.addEventListener('message', (e) => {
            // data: 从其他 window 传递过来的数据副本 
            console.log(e.data);
            // origin: 调用 postMessage 时,消息发送窗口的 origin
            console.log(e.origin);
            // source: 发送数据一方 window 对象
            console.log(e.source);
    
            if (e.origin !== 'http://127.0.0.1:5500') return;
    
            // 发送消息给 domain1
            window.parent.postMessage('b.html 发送的消息', e.origin);
        }, false);
    </script>
    

window.name + iframe

  1. 实现原理:

    • 每个窗口都有自己独立的 window.name
    • 当修改一个窗口的 src 属性的时候,不会影响这个窗口的 window.name 属性;
    • window.name 可以存储不超过 2M 的数据,传递的数据都会变成 string 类型;
    • window.name 属性值在文档刷新后依旧具有存在的能力;
  2. 具体实现:

    • A 域:a.html、proxy.html;
    • B 域:b.html;
    • 若 a 想要拿到 b 的数据,需要把 iframe 的指向重新指回 A 相同的源下 proxy.html;
  3. 实现代码

    • a.html http://127.0.0.1:5500/proxy/a.html
      <script>
          var proxy = function (url, callback) {
              var state = 0;
      
              var iframe = document.createElement('iframe');
              iframe.src = url;// 加载跨域页面
              document.body.appendChild(iframe);
      
              // onload 事件会触发 2 次:
              //  第 1 次加载跨域页 b.html,并留存数据于 window.name
              //  第 2 次加载同域页 proxy.html,获取 window.name
              iframe.onload = function () {
                  if (state === 0) {
                      // 第 1 次 onload 成功后,切换到同域代理页面
                      // 想要正常拿到 B 中的内容,还需要把 iframe 的指向,重新指回 A 相同的源下
                      iframe.contentWindow.location = 'http://127.0.0.1:5500/proxy/proxy.html';
                      state = 1;
                  } else if (state === 1) {
                      // 第 2 次 onload(同域 proxy 页)成功后,读取同域 window.name 中数据
                      callback(iframe.contentWindow.name);
                      // 获取数据以后销毁这个 iframe,释放内存,也保证了安全(不被其他域访问)
                      iframe.contentWindow.document.write('');
                      iframe.contentWindow.close();
                      document.body.removeChild(iframe);
                  }
              };
          };
      
          // 请求跨域 b 页面数据
          proxy('http://127.0.0.1:5501/b.html', function (data) {
              alert(data);
          });
      </script>
      
    • proxy.html http://127.0.0.1:5500/proxy/proxy.html
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
      </head>
      <body>
      </body>
      </html>
      
    • b.html http://127.0.0.1:5501/b.html
      <script type="text/javascript">
          var person = {
              name: "张三",
              age: "26",
          }
          window.name = JSON.stringify(person)
      </script>
      

window.location.hash + iframe

  1. 该方法跟 window.name 类似

  2. 具体实现

    • 访问 a.html 会加载 b.html,并把值放在 b 的 iframe 的 hash 中;
    • 然后 b 载入后,会加载 c.html,并把值放在 c 的 iframe 的 hash 中;
    • 而 a 和 c 是同域的,那么也就是说 c 的 hash 可以直接复制给 a 的 hash,这样 a 就得到了 b 的值;
  3. 代码实现

    • a.html http://localhost:5500
      <body>
        <iframe id="iframe" src="http://127.0.0.1:5501/B.html" style="display:none;"></iframe>
      </body>
      <script>
          let iframe = document.getElementById('iframe');
          // => 向 B.html传 hash值
          iframe.onload = function () {
              // 后面多传了一个哈希值 msg=zhufengpeixun
              iframe.src = 'http://127.0.0.1:5501/B.html#name=zhangsan';
          }
          function func(res) {
              alert(res);
          }
      </script>
      
    • b.html http://localhost:5501
      <body>
        <iframe id="iframe" src="http://127.0.0.1:5500/C.html" style="display:none;"></iframe>
      </body>
      <script>
          let iframe = document.getElementById('iframe');
          // => 监听 A 传来的 HASH 值改变,再传给 C.html
          window.onhashchange = function () {
              iframe.src = "http://127.0.0.1:5500/C.html" + location.hash;
          }
      </script>
      
    • c.html http://localhost:5500
      <script>
          // => 监听 B 传来的 HASH 值
          window.onhashchange = function () {
              // => 再通过操作同域 A 的 js 回调,将结果传回
              window.parent.parent.func(location.hash);
          };
      </script>
      

document.domain + iframe

  1. document.domain 用来得到当前网页的域名

    • 比如在百度页面控制台中输入:
      alert(document.domain); //"www.baidu.com"
      
    • 也可以给 document.domain 属性赋值,不过是有限制的,只能赋成当前的域名或者一级域名,比如:
      alert(document.domain = "baidu.com");     //"baidu.com"
      alert(document.domain = "www.baidu.com"); //"www.baidu.com"
      
    • 上面的赋值都是成功的,因为 www.baidu.com 是当前的域名,而 baidu.com 是一级域名,但是下面的赋值就会出来"参数无效"的错误,比如:
      alert(document.domain = "qq.com");     //参数无效  报错
      alert(document.domain = "www.qq.com"); //参数无效  报错
      
    • 因为 qq.combaidu.com 的一级域名不相同,所以会有错误出现,这是为了防止有人恶意修改 document.domain 来实现跨域偷取数据;
  2. 利用 document.domain 实现跨域

    • 前提条件:这两个域名必须属于同一个一级域名!而且所用的协议,端口都要一致,否则无法利用 document.domain 进行跨域,Javascript 出于对安全性的考虑,而禁止两个或者多个不同域的页面进行互相操作,而相同域的页面在相互操作的时候不会有任何问题;
    • 比如:
      • baidu.com 的一个网页(baidu.html)里面利用 iframe 引入了一个 qq.com 里的一个网页(qq.html);
      • 这时在 baidu.html 里面可以看到 qq.html 里的内容,但是却不能利用 javascript 来操作它,因为这两个页面属于不同的域,在操作之前,js 会检测两个页面的域是否相等,如果相等,就允许其操作,如果不相等,就会拒绝操作;
      • 这里不可能把 baidu.html 与 qq.html 利用 JS 改成同一个域的,因为它们的一级域名不相等(强制用 JS 将它们改成相等的域的话会报跟上面一样的"参数无效错误");
      • 但如果在 baidu.html 里引入 baidu.com 里的另一个网页,是不会有这个问题的,因为域相等;
    • 另一种情况,有两个子域名:news.baidu.com(news.html)、map.baidu.com(map.html)
      • news.baidu.com 里的一个网页(news.html)引入了 map.baidu.com 里的一个网页(map.html),这时 news.html 里同样是不能操作 map.html 里面的内容的,因为 document.domain 不一样,一个是news.baidu.com,另一个是 map.baidu.com
      • 这时就可以通过 Javascript 将两个页面的 domain 改成一样的,需要在 a.html 里与 b.html 里都加入document.domain = “baidu.com”;
      • 这样这两个页面就可以互相操作了,也就是实现了同一一级域名之间的"跨域";
  3. 实现代码

    • news.baidu.com 下的 news.html 页面
      <script>
          document.domain = 'baidu.com';
      
          var ifr = document.createElement('iframe');
          ifr.src = 'map.baidu.com/map.html';
          ifr.style.display = 'none';
          document.body.appendChild(ifr);
      
          ifr.onload = function () {
              var doc = ifr.contentDocument || ifr.contentWindow.document;
              // 这里可以操作 map.baidu.com 下的 map.html 页面
              var oUl = doc.getElementById('ul1');
              alert(oUl.innerHTML);
              ifr.onload = null;
          };
      </script>
      
    • map.baidu.com 下的 map.html 页面
      <ul id="ul1">我是map.baidu.com中的ul</ul>
      <script>
        document.domain = 'baidu.com';
      </script>
      

面试题

表单可以跨域吗

  1. Form 表单可以跨域是因为要保持兼容性,当请求到另一个域名后,原页面得脚本无法获取新页面中得内容,提交的 Form 表单数据不需要返回,所以浏览器认为是安全得行为,所以浏览器不会阻止 Form 表单跨域;

  2. ajax 请求到另一个域名后,是需要获取返回的数据,浏览器认为不安全,所以会阻止这个请求行为;

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于产品、设计、开发的问题和看法,还有技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程

了解更多

目录

  1. 1. nginx
  2. 2. JSONP
  3. 3. CORS
  4. 4. proxy 客户端代理
  5. 5. web socket
  6. 6. postMessage
  7. 7. window.name + iframe
  8. 8. window.location.hash + iframe
  9. 9. document.domain + iframe
  10. 10. 面试题
    1. 10.1. 表单可以跨域吗