欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

利用window.name+iframe跨域获取数据详解 有大用

 前文提到用jsonp的方式来跨域获取数据,本文为大家介绍下如何利用window.name+iframe跨域获取数据。

  首先我们要简单了解下window.name和iframe的相关知识。iframe是html的一个标签,可以在网页中创建内联框架,有个src属性(指向文件地址,html、php等)可以选择内联框架的内容,可以看个例子(猛戳这里),大概了解下就行了。window.name(一般在js代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,而它自然也有window.name的属性,window.name属性的神奇之处在于name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。

  跨域解决方案似乎可以呼之欲出了,假设index.html页面请求远端服务器的数据,我们在该页面下新建一个iframe标签,该iframe的src属性指向服务器文件地址(利用iframe标签的跨域能力),服务器文件里设置好window.name的值(也就是该iframe的contentWindow的name值),然后在index.html里读取该iframe的window.name值,一切似乎水到渠成,代码如下:

复制代码
<body>
  <script type="text/javascript"> 
    iframe = document.createElement('iframe'),
    iframe.src = 'http://localhost:8080/data.php';
    document.body.appendChild(iframe);
    iframe.onload = function() {
      console.log(iframe.contentWindow.name)
    };
  </script>
</body>
复制代码
<?php
  echo '<script> window.name = "{\"name\":\"hanzichi\", \"age\":10}"; </script>'
?>

  但是不幸的是,报错了..

  提示啥协议、主机、端口三者要一致,这不是赤裸裸地告诉你跨域了么!为什么会这样,因为规定如果index.html页面和和该页面里的iframe框架的src如果不同源,则也无法操作框架里的任何东西,所以就取不到iframe框架的name值了,告诉你我们不是一家的,你也休想得到我这里的数据。既然要同源,那就换个src去指,前面说了无论怎样加载window.name值都不会变化,于是我们在index.html相同目录下,新建了个proxy.html的空页面,修改代码如下:

复制代码
<body>
  <script type="text/javascript"> 
    iframe = document.createElement('iframe'),
    iframe.src = 'http://localhost:8080/data.php';
    document.body.appendChild(iframe);
    iframe.onload = function() {
      iframe.src = 'http://localhost:81/cross-domain/proxy.html';
      console.log(iframe.contentWindow.name)
    };
  </script>
</body>
复制代码

  理想似乎很美好,在iframe载入过程中,迅速重置iframe.src的指向,使之与index.html同源,那么index页面就能去获取它的name值了!但是现实是残酷的,iframe在现实中的表现是一直不停地刷新,也很好理解,每次触发onload时间后,重置src,相当于重新载入页面,又触发onload事件,于是就不停地刷新了(但是需要的数据还是能输出的)。修改后代码如下:

复制代码
<body>
  <script type="text/javascript"> 
    iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    var state = 0;
    
    iframe.onload = function() {
      if(state === 1) {
          var data = JSON.parse(iframe.contentWindow.name);
          console.log(data);
          iframe.contentWindow.document.write('');
          iframe.contentWindow.close();
        document.body.removeChild(iframe);
      } else if(state === 0) {
          state = 1;
          iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html';
      }
    };

    iframe.src = 'http://localhost:8080/data.php';
    document.body.appendChild(iframe);
  </script>
</body>
复制代码

总结

  能使用这种方式跨域,有几个条件必不可少。

  1. iframe标签的跨域能力
  2. window.name属性值在文档刷新后依旧存在的能力

  再简单了解下window和contentWindow的一些知识。浏览器就会为原始文档创建一个 window 对象,再为每个框架(iframe)创建额外的 window 对象。这些额外的对象是原始窗口的子窗口,可能被原始窗口中发生的事件所影响。例如,关闭原始窗口将导致关闭全部子窗口。contentWindow属性是指指定的frame或者iframe所在的window对象。

使用方法

  很多人或许只关注使用方法,而对原理不怎么感冒。此法相对于jsonp复杂,使用方法也更复杂些。

  服务端一般输出一段js代码,例如下面这样:

<?php
  echo '<script> window.name = "{\"name\":\"hanzichi\", \"age\":10}"; </script>'
?>

  window.name的值是字符串形式,也是需要传递给客户端的数据(当然如果有需要服务端也可以先处理传过来的数据,这里仅为只有数据回传),有需要自己再去解析(如字符串->json数据)。

  index.html页面,我把方法封装成了函数:

复制代码
<body>
  <script type="text/javascript"> 
    function crossDomain(url, fn) {
      iframe = document.createElement('iframe');
      iframe.style.display = 'none';
      var state = 0;
    
      iframe.onload = function() {
        if(state === 1) {
          fn(iframe.contentWindow.name);
          iframe.contentWindow.document.write('');
          iframe.contentWindow.close();
          document.body.removeChild(iframe);
        } else if(state === 0) {
          state = 1;
          iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html';
        }
      };

      iframe.src = url;
      document.body.appendChild(iframe);
    } 
    
    // 调用
    // 服务器地址
    var url = 'http://localhost:8080/data.php';
    crossDomain(url, function(data) { // 处理数据 data就是window.name的值(string)
      var data = JSON.parse(iframe.contentWindow.name);
      console.log(data);
    });
  </script>
</body>
复制代码

  这里还有一点小问题,ie的兼容(iframe的onload ie下似乎要用attachEvent,以及json对于ie的兼容),有兴趣的朋友自己去研究了(可以参考下参考文章);另外proxy代理页面可以没有这个文件,会报404但是不影响功能(但是路径一定要和index页面同源)。

  还有一点值得思考的是iframe的src重定向的时候,代码:

iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html';

  这时iframe.src指向的还是服务端页面,这里的代码如果用iframe.src = 'http://localhost:81/cross-domain/proxy.html';代替也是可以输出结果的,聪明的你知道区别吗?

参考

  1. js中几种实用的跨域方法原理详解
  2. JS 使用window.name跨域实践
  3. 使用 window.name 解决跨域问题
posted on 2015-07-06 09:30 韩子迟 阅读(11509) 评论(19编辑 收藏
 

Feedback

#1楼 2015-07-06 10:43 早起的菜鸟  

 

学习了
  

 

#2楼 2015-07-06 10:48 Joshua.Jiang  

 

@ 早起的菜鸟
fd
  

 

#3楼 2015-07-06 14:19 影子卓卓  

 

这招是不是各种火车票助手的办法?
  

 

#4楼[楼主2015-07-06 14:59 韩子迟  

 

@ 影子卓卓
sorry 这个暂时没了解过
  

 

#5楼 2015-07-07 08:34 游云  

 

需要中间加入一个 about:blank来解决window.name刷新存在的问题,主要的是window.name跨域存在很多问题,不推荐使用
  

 

#6楼[楼主2015-07-07 09:19 韩子迟  

 

@ 游云
谢谢前辈指导
顺便请教下为啥要加个代理页?
  

 

#7楼 2015-07-08 00:08 Lumia1020  

 

这时iframe.src指向的还是服务端页面,这里的代码如果用iframe.src = 'http://localhost:81/cross-domain/proxy.html';代替也是可以输出结果的,聪明的你知道区别吗? 

这个还是不懂 楼主可以讲一下吗 谢谢
  

 

#8楼 2015-07-08 08:59 游云  

 

@ 韩子迟
不太记得了,2年前写的了,
好像就是解决刷新的时候还存在window.name的情况,
这样设计的话也就不会污染主页面的window.name,可以实现同时跨多个域名通信
window.name 方案问题很多,请看这偏博客 http://www.alloyteam.com/2013/11/the-second-version-universal-solution-iframe-cross-domain-communication/
  

 

#9楼[楼主2015-07-08 09:18 韩子迟  

 

@ 游云
多谢~
  

 

#10楼[楼主2015-07-08 09:25 韩子迟  

 

@ Lumia1020
说下我的理解
每个iframe都有个子窗口(可以用iframe.contentWindow获取)
重置iframe.src 或者 iframe.contentWindow.location.href都会引起子窗口页面的刷新,以及内容的变化(指向重置后的url)
如果重置的是iframe.src,那么刷新后iframe.src === iframe.contentWindow.location.href
如果重置的是iframe.contentWindow.location.href,iframe.src 值不变
  

 

#11楼[楼主2015-07-08 09:30 韩子迟  

 

@ Lumia1020
其实可以这样理解,iframe里面包含了子窗口,所以操作iframe的内容会影响子窗口,反之操作子窗口的内容则不会影响“父辈”iframe的属性。而如果子窗口和父窗口要通信的话必须同源,所以改变子窗口的地址就行了
1
iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html';

当然改变iframe.src也行,会影响iframe.contentWindow.location的值
  

 

#12楼[楼主2015-07-08 09:37 韩子迟  

 

@ Lumia1020
<img data-cke-saved-src="http://images0.cnblogs.com/blog2015/675542/201507/080937040495507.png" src="http://images0.cnblogs.com/blog2015/675542/201507/080937040495507.png" alt="" border="0" "="" style="border-width: 0px; border-style: initial; max-width: 400px;">
  

 

#13楼 2015-07-08 10:44 Lumia1020  

 

@ 韩子迟
刚看到 谢谢热心回复 我先看一下 我 JS 有点差 
十分感谢
  

 

#14楼 2015-07-08 10:51 Lumia1020  

 

其实 楼主还有一个问题 我想问一下 
就是比如 www.a.com/index.html 下面我设置其 window.name='index.html' 
www.a.com/index.html 有包括一个 iframe 该 iframe 的 src 指向 www.b.com/indexNew.html 我在该下面通过 JS 设置其 window.name='indexNew.html'
那么我在 www.a.com/index.html 下再次通过 iframe 拿到 iframe 的 name 和 ifram 的 contentWindow 的 name 两个值是咋样的 这个地方我有点没有搞清楚
不好意思 我理解能力有点差 请多多见谅
  

 

#15楼 2015-07-08 10:57 Lumia1020  

 

@ 韩子迟
谢谢 这样一讲我理解了 谢谢你
楼主好人 好人一生平安
  

 

#16楼[楼主2015-07-08 11:12 韩子迟  

 

@ Lumia1020
不客气~
  

 

#17楼 2015-07-18 19:20 苏生不惑  

 

父窗口和子窗口的函数可以相互调用吗
  

 

#18楼[楼主2015-07-19 09:21 韩子迟  

 

@ 苏生不惑
同源的话,可以。
  
来自 http://www.cnblogs.com/zichi/p/4620656.html
 
普通分类: