JS常用跨域方法实现原理解析


Posted in Javascript onDecember 09, 2020

这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据。只要协议、域名、端口有任何一个不同,都被当作是不同的域。

下表给出了相对http://store.company.com/dir/page.html同源检测的结果:

JS常用跨域方法实现原理解析

要解决跨域的问题,我们可以使用以下几种方法:

通过jsonp跨域

在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。

比如,有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是http://example.com/data.php,那么a.html中的代码就可以这样:

JS常用跨域方法实现原理解析

我们看到获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,但是你用其他的也一样。当然如果获取数据的jsonp地址页面不是你自己能控制的,就得按照提供数据的那一方的规定格式来操作了。

因为是当做一个js文件来引入的,所以http://example.com/data.php返回的必须是一个能执行的js文件,所以这个页面的php代码可能是这样的:

JS常用跨域方法实现原理解析

最终那个页面输出的结果是:

JS常用跨域方法实现原理解析

所以通过http://example.com/data.php?callback=dosomething得到的js文件,就是我们之前定义的dosomething函数,并且它的参数就是我们需要的json数据,这样我们就跨域获得了我们需要的数据。

这样jsonp的原理就很清楚了,通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。

知道jsonp跨域的原理后我们就可以用js动态生成script标签来进行跨域操作了,而不用特意的手动的书写那些script标签。如果你的页面使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。

JS常用跨域方法实现原理解析

原理是一样的,只不过我们不需要手动的插入script标签以及定义回掉函数。jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

 2、通过修改document.domain来跨子域

浏览器都有一个同源策略,其限制之一就是第一种方法中我们说的不能通过ajax的方法去请求不同源中的文档。 它的第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。有一点需要说明,不同的框架之间(父子或同辈),是能够获取到彼此的window对象的,但蛋疼的是你却不能使用获取到的window对象的属性和方法(html5中的postMessage方法是一个例外,还有些浏览器比如ie6也可以使用top、parent等少数几个属性),总之,你可以当做是只能获取到一个几乎无用的window对象。比如,有一个页面,它的地址是http://www.example.com/a.html  , 在这个页面里面有一个iframe,它的src是http://example.com/b.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的:

JS常用跨域方法实现原理解析

这个时候,document.domain就可以派上用场了,我们只要把http://www.example.com/a.html 和 http://example.com/b.html这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。例如:a.b.example.com 中某个文档的document.domain 可以设成a.b.example.com、b.example.com 、example.com中的任意一个,但是不可以设成 c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。

在页面 http://www.example.com/a.html 中设置document.domain:

JS常用跨域方法实现原理解析

在页面 http://example.com/b.html 中也设置document.domain,而且这也是必须的,虽然这个文档的domain就是example.com,但是还是必须显示的设置document.domain的值:

JS常用跨域方法实现原理解析

这样我们就可以通过js访问到iframe中的各种属性和对象了。

不过如果你想在http://www.example.com/a.html 页面中通过ajax直接请求http://example.com/b.html 页面,即使你设置了相同的document.domain也还是不行的,所以修改document.domain的方法只适用于不同子域的框架间的交互。如果你想通过ajax的方法去与不同子域的页面交互,除了使用jsonp的方法外,还可以用一个隐藏的iframe来做一个代理。原理就是让这个iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe,这样我们就可以让iframe去发送ajax请求,然后收到的数据我们也可以获得了。

3、使用window.name来进行跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

比如:有一个页面a.html,它里面有这样的代码:

JS常用跨域方法实现原理解析

再看看b.html页面的代码:

JS常用跨域方法实现原理解析

a.html页面载入后3秒,跳转到了b.html页面,结果为:

JS常用跨域方法实现原理解析

我们看到在b.html页面上成功获取到了它的上一个页面a.html给window.name设置的值。如果在之后所有载入的页面都没对window.name进行修改的话,那么所有这些页面获取到的window.name的值都是a.html页面设置的那个值。当然,如果有需要,其中的任何一个页面都可以对window.name的值进行修改。注意,window.name的值只能是字符串的形式,这个字符串的大小最大能允许2M左右甚至更大的一个容量,具体取决于不同的浏览器,但一般是够用了。

上面的例子中,我们用到的页面a.html和b.html是处于同一个域的,但是即使a.html与b.html处于不同的域中,上述结论同样是适用的,这也正是利用window.name进行跨域的原理。

下面就来看一看具体是怎么样通过window.name来跨域获取数据的。还是举例说明。

比如有一个www.example.com/a.html页面,需要通过a.html页面里的js来获取另一个位于不同域上的页面www.cnblogs.com/data.html里的数据。

data.html页面里的代码很简单,就是给当前的window.name设置一个a.html页面想要得到的数据值。data.html里的代码:

JS常用跨域方法实现原理解析

那么在a.html页面中,我们怎么把data.html页面载入进来呢?显然我们不能直接在a.html页面中通过改变window.location来载入data.html页面,因为我们想要即使a.html页面不跳转也能得到data.html里的数据。答案就是在a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取data.html的数据,然后a.html再去得到iframe获取到的数据。

充当中间人的iframe想要获取到data.html的通过window.name设置的数据,只需要把这个iframe的src设为www.cnblogs.com/data.html就行了。然后a.html想要得到iframe所获取到的数据,也就是想要得到iframe的window.name的值,还必须把这个iframe的src设成跟a.html页面同一个域才行,不然根据前面讲的同源策略,a.html是不能访问到iframe里的window.name属性的。这就是整个跨域过程。

看下a.html页面的代码:

JS常用跨域方法实现原理解析

上面的代码只是最简单的原理演示代码,你可以对使用js封装上面的过程,比如动态的创建iframe,动态的注册各种事件等等,当然为了安全,获取完数据后,还可以销毁作为代理的iframe。网上也有很多类似的现成代码,有兴趣的可以去找一下。

通过window.name来进行跨域,就是这样子的。

4、使用HTML5中新引进的window.postMessage方法来跨域传送数据

window.postMessage(message,targetOrigin)  方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。

调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 *  。

需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。

上面所说的向其他window对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。在讨论第二种方法的时候,我们说过,不同域的框架间是可以获取到对方的window对象的,而且也可以使用window.postMessage这个方法。下面看一个简单的示例,有两个页面

JS常用跨域方法实现原理解析

JS常用跨域方法实现原理解析

我们运行a页面后得到的结果:

JS常用跨域方法实现原理解析

我们看到b页面成功的收到了消息。

使用postMessage来跨域传送数据还是比较直观和方便的,但是缺点是IE6、IE7不支持,所以用不用还得根据实际需要来决定。

结语:

除了以上几种方法外,还有flash、在服务器上设置代理页面等跨域方式,这里就不做介绍了。

以上四种方法,可以根据项目的实际情况来进行选择应用,个人认为window.name的方法既不复杂,也能兼容到几乎所有浏览器,这真是极好的一种跨域方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript跑马灯悬停放大效果实现代码
Dec 12 Javascript
扩展js对象数组的OrderByAsc和OrderByDesc方法实现思路
May 17 Javascript
JS获取IP、MAC和主机名的五种方法
Nov 14 Javascript
node.js入门教程迷你书、node.js入门web应用开发完全示例
Apr 06 Javascript
js利用prototype调用Array的slice方法示例
Jun 09 Javascript
通过伪协议解决父页面与iframe页面通信的问题
Apr 05 Javascript
JQuery中上下文选择器实现方法
May 18 Javascript
基于javascript实现精确到毫秒的倒计时限时抢购
Apr 17 Javascript
vue组件详解之使用slot分发内容
Apr 09 Javascript
在AngularJs中设置请求头信息(headers)的方法及不同方法的比较
Sep 04 Javascript
Vue快速实现通用表单验证的示例代码
Jan 09 Javascript
vue项目配置使用flow类型检查的步骤
Mar 18 Javascript
video.js添加自定义组件的方法
Dec 09 #Javascript
手把手教你如何编译打包video.js
Dec 09 #Javascript
ES6 十大特性简介
Dec 09 #Javascript
Js实现粘贴上传图片的原理及示例
Dec 09 #Javascript
再也不怕 JavaScript 报错了,怎么看怎么处理都在这儿
Dec 09 #Javascript
使用typescript快速开发一个cli的实现示例
Dec 09 #Javascript
ES6中的类(Class)示例详解
Dec 09 #Javascript
You might like
德劲1104的电路分析与改良
2021/03/01 无线电
本地计算机无法启动Apache故障处理
2014/08/08 PHP
php修改文件上传限制方法汇总
2015/04/07 PHP
jquery插件如何使用 jQuery操作Cookie插件使用介绍
2012/12/15 Javascript
如何在一个页面显示多个百度地图
2013/04/07 Javascript
JavaScript中instanceof运算符的用法总结
2013/11/19 Javascript
javascript通过className来获取元素的简单示例代码
2014/01/10 Javascript
jquery等待效果示例
2014/05/01 Javascript
ECMAScript 5中的属性描述符详解
2015/03/02 Javascript
js下拉选择框与输入框联动实现添加选中值到输入框的方法
2015/08/17 Javascript
NodeJS连接MongoDB数据库时报错的快速解决方法
2016/05/13 NodeJs
自动化测试读写64位操作系统的注册表
2016/08/15 Javascript
Vue中使用vue-i18插件实现多语言切换功能
2018/04/25 Javascript
深入浅出理解JavaScript闭包的功能与用法
2018/08/01 Javascript
对Vue table 动态表格td可编辑的方法详解
2018/08/28 Javascript
nodejs微信开发之接入指南
2019/03/17 NodeJs
layer弹出层显示在top顶层的方法
2019/09/11 Javascript
rabbitmq(中间消息代理)在python中的使用详解
2017/12/14 Python
Python中装饰器高级用法详解
2017/12/25 Python
Python设计模式之建造者模式实例详解
2019/01/17 Python
Python选择网卡发包及接收数据包
2019/04/04 Python
PyQt5 多窗口连接实例
2019/06/19 Python
Python3使用PySynth制作音乐的方法
2019/09/09 Python
Python进程池Pool应用实例分析
2019/11/27 Python
推荐8款常用的Python GUI图形界面开发框架
2020/02/23 Python
基于django micro搭建网站实现加水印功能
2020/05/22 Python
Canvas 文本转粒子效果的实现代码
2019/02/14 HTML / CSS
使用HTML5中的contentEditable来将多行文本自动增高
2016/03/01 HTML / CSS
英国家居用品和家居装饰品购物网站:Cox & Cox
2019/08/25 全球购物
求两个数的乘积和商数,该作用由宏定义来实现
2013/03/13 面试题
银行自荐信范文
2013/10/07 职场文书
人事部岗位职责范本
2014/03/05 职场文书
档案保密承诺书
2014/06/03 职场文书
2014教师党员自我评议(5篇)
2014/09/20 职场文书
Nginx解决403 forbidden的完整步骤
2021/04/01 Servers
Windows Server 2012 R2 磁盘分区教程
2022/04/29 Servers