本文深入解析JavaScript中解决跨域问题的四种核心机制。首先阐述同源策略对Ajax请求的限制,随后详细讲解利用script标签特性的JSONP实现原理及jQuery封装用法;接着分析通过修改document.domain解决子域交互的局限性与代理方案;进而探讨利用window.name持久化特性进行跨域数据传递的流程;最后介绍HTML5标准API window.postMessage的安全通信方式及其浏览器兼容性。
本文所指的JS跨域,旨在探讨如何利用JavaScript在不同域名之间执行数据传输或通信操作。典型场景包括通过Ajax向非同源服务器请求数据,或在JS中读取不同域框架(iframe)内的数据。判定跨域的标准极为严格:只要协议、域名或端口中任意一项存在差异,即被视为不同源。
下表展示了基于http://store.company.com/dir/page.html这一基准URL的同源检测结果:

针对跨域限制,业界主要采用以下几种技术手段进行规避与解决:
通过JSONP实现跨域
在JavaScript中,直接使用XMLHttpRequest向不同源服务器发起Ajax请求会被浏览器安全策略拦截。然而,网页允许引入来自不同域的JavaScript脚本文件。JSONP技术正是基于这一特性,通过动态加载脚本文件来实现跨域数据交互。
假设存在一个a.html页面,其内部逻辑需要通过Ajax获取位于不同域上的JSON数据,目标地址为http://example.com/data.php。此时,a.html中的实现代码可设计如下:

观察上述请求地址,可以发现URL末尾附加了一个callback参数。虽然业界惯例使用该名称,但实际使用时可自定义其他名称。需注意,若目标JSONP接口由第三方提供,则必须严格遵循其规定的参数格式进行调用。
由于该请求被视为加载一个JavaScript文件,因此http://example.com/data.php返回的内容必须是可执行的JS代码。该PHP页面的后端逻辑可能如下所示:

最终,该服务端页面输出的结果如下:

当通过http://example.com/data.php?callback=dosomething发起请求时,服务器返回的JavaScript文件实际上执行了预先定义的dosomething函数,并将所需的JSON数据作为参数传入。由此,前端成功获取了跨域数据。
JSONP的核心原理在于:通过script标签引入外部JS文件,文件加载成功后会触发URL参数中指定的回调函数,并将JSON数据作为参数传递。因此,该方案要求服务端页面做出相应配合,返回特定格式的JS代码。
理解JSONP原理后,开发者可通过JavaScript动态生成script标签来实现跨域,而无需手动编写静态标签。若项目使用jQuery框架,可通过其封装的方法便捷地执行JSONP操作。

其底层原理与手动实现一致,但jQuery简化了流程,无需手动插入script标签或定义回调函数。jQuery会自动生成一个全局函数以替代callback=?中的问号部分,数据获取完成后自动销毁该函数,实质是充当临时代理角色。$.getJSON方法会自动检测跨域状态:若未跨域则调用普通Ajax;若跨域则以异步加载JS文件的方式触发JSONP回调。
2、通过修改document.domain实现子域跨域
浏览器同源策略包含两项主要限制:一是禁止通过Ajax请求不同源文档(如前文所述);二是禁止不同域框架间的JS交互。需注意的是,不同框架(父子或兄弟关系)虽能相互获取对方的window对象,但通常无法访问其属性和方法(HTML5的postMessage为例外,部分旧版浏览器如IE6支持top、parent等少数属性)。换言之,获取到的window对象几乎不可用。例如,页面http://www.example.com/a.html包含一个src为http://example.com/b.html的iframe。由于两者域名不同,页面JS无法直接获取iframe内的内容:

此时可借助document.domain属性。将http://www.example.com/a.html与http://example.com/b.html的document_domain均设置为相同的主域名即可。但需注意,document.domain的设置存在严格限制:只能设置为当前域或其上级父域,且主域名必须一致。例如,a.b.example.com的文档可将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的代码如下:

在页面http://example.com/b.html中也必须设置document.domain。尽管该文档的domain原本即为example.com,但仍需显式设置该值以确保策略生效:

完成上述设置后,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返回的数据。
3、使用window.name进行跨域
window对象拥有一个name属性,其显著特征是:在一个窗口(window)的生命周期内,该窗口加载的所有页面共享同一个window.name值。每个页面均拥有对该属性的读写权限。window.name的值会持久保留在窗口载入过的所有页面中,即使新页面加载也不会重置该值。
例如,存在一个页面a.html,其内部代码逻辑如下:

对应的b.html页面代码如下:

a.html页面加载3秒后跳转至b.html,运行结果如下:

结果显示,b.html成功获取了由前一个页面a.html设置的window.name值。若后续加载的页面未修改window.name,则所有页面获取的值均保持为a.html设置的初始值。当然,任何页面均可修改该值。需注意,window.name的值仅限字符串形式,容量最大约为2MB(具体限制因浏览器而异),通常足以满足需求。
上述示例中,a.html与b.html处于同一域。即便两者处于不同域,该结论依然成立,这正是利用window.name进行跨域的原理基础。
接下来具体演示如何通过window.name实现跨域数据获取,举例说明如下。
假设存在www.example.com/a.html页面,需通过JS获取位于不同域的www.cnblogs.com/data.html中的数据。
data.html页面的代码极为简单,仅需将当前页面想要获取的数据赋值给window.name。其代码如下:

在a.html中,如何加载data.html页面?显然不能直接通过改变window.location加载,因为目标是a.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封装该过程,例如动态创建iframe、注册各类事件等。出于安全考虑,数据获取完成后建议销毁代理iframe。网上已有许多类似现成代码可供参考。
通过window.name进行跨域的操作流程即如上所述。
4、使用HTML5新增的window.postMessage方法跨域
window.postMessage(message, targetOrigin)是HTML5引入的新特性,用于向其他window对象发送消息,无论目标窗口是否同源。目前IE8+、Firefox、Chrome、Opera等主流浏览器均已支持此方法。
调用postMessage方法的window对象指接收消息的目标窗口。该方法第一个参数message为发送的消息,类型必须为字符串;第二个参数targetOrigin用于限定接收消息的窗口所在域,若不限定域,可使用通配符*。
接收消息的window对象需通过监听自身的message事件来获取传递的消息,消息内容存储在该事件对象的data属性中。
前文所述向其他window对象发送消息,通常指包含多个框架的页面场景,因为每个框架均拥有独立的window对象。在讨论第二种方法时曾提及,不同域框架间可获取对方window对象,且可使用window.postMessage方法。以下展示一个简单示例,涉及两个页面:


运行a页面后得到的结果如下:

结果显示,b页面成功接收到了消息。
使用postMessage进行跨域数据传送直观且便捷,但其缺点在于IE6和IE7不支持,因此是否采用需根据实际项目需求决定。
结语:
除上述方法外,还存在Flash通信、服务器端代理页面等其他跨域方案,此处不再赘述。
以上四种方法可根据项目实际情况灵活选用。个人认为,window.name方法既不过于复杂,又能兼容几乎所有浏览器,是一种极佳的跨域解决方案。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。