浏览器跨域解决方案
什么是跨域
浏览器的同源策略会导致跨域,这里同源策略又分为以下两种 DOM 同源策略:
- XmlHttpRequest 同源策略:禁止使用XHR对象向不同源的服务器地址发起 HTTP 请求。
- 禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
即不能通过 ajax 的方法去请求不同源中的文档、浏览器中不同域的框架之间是不能进行js的交互操作的。
只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。
还有一点比较重要,为了安全,限制跨域是浏览器的行为,而不是 JS / 服务端的行为。你也可以自己开发一个浏览器,或者拿开源代码改,使得自己开发的浏览器能够跨域。
- 哪些情况属于跨域:
- 协议不同,如 http, https;
- 端口不同;
- 主域相同,子域不同;(域名不同)
- 主域不同;(域名不同)
- ip 地址和域名之间也算是跨域,浏览器不会自动做 ip 域名的映射;
- 限制范围
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM 无法获得。
- AJAX 请求不能发送。
- 解决方案
- jsonp
- cors
- WebSocket
- 服务器代理转发
- postMessage
AJAX请求不同源的跨域
一、 JSONP(浏览器与服务端的通信)
为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作 JSONP。该协议的一个要点就是通过创建一个 script 标签,将 src 设置为目标请求,插入到 dom 中,服务器接受该请求并返回数据,数据通常被包裹在回调钩子中。
直接打开 index.html 文件浏览器端会报错。就是同源限制造成的。
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
<script>
function request(type, url, data) {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log(xhr.responseText)
} else {
console.log(xhr.status)
}
}
}
xhr.open(type, url, true)
xhr.send(data)
}
request('get', 'http://localhost:3000/user', null) // 网页默认端口 80 or 8000
</script>
</html>
1. JSONP 的实现原理(利用 Script 标签不受跨域限制而形成的一种方案)
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写,是应用 JSON 的一种方式。
JSONP 的实现原理很简单,利用 <script>
标签没有同源限制的特点,也就是 <script>
的src链接可以访问不同源的。不受同源限制的还有 <img>
、<iframe>
、<link>
,对应这两个标签实现的跨域方法也有,比如图片 ping 等。(图片 ping:可以访问任何 url,一般用来进行点击追踪,做页面分析常用的方法。缺点:不能访问响应文本,只能监听是否响应)
下面为允许跨域资源嵌入的示例,即一些不受同源策略影响的标签示例:
<script src="..."></script>
标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。<link rel="stylesheet" href="...">
标签嵌入 CSS。由于 CSS 的松散的语法规则,CSS 的跨域需要一个设置正确的 Content-Type 消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari 和 Opera。<img>
嵌入图片。支持的图片格式包括 PNG、JPEG、GIF、BMP、SVG<video>
和<audio>
嵌入多媒体资源。<object>
,<embed>
和<applet>
的插件。@font-face
引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。<frame>
和<iframe>
载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互。
我们访问服务端,一般是获取存JSON数据,而JSONP则返回的是,包含函数的数据,将我们需要的 JSON 数据作为函数的参数。
即在客户端一般通过 <script>
标签的 src 访问带有 callback 查询参数的请求,来获取返回带有函数的数据,然后执行它,即可拿到这份数据(服务端注入的数据)完成跨域访问。
// 服务器返回的内容: 以字符串的形式返回
callback({"name": "xuthus"};
由于使用 script 标签的 src 属性,因此只支持get方法。
JSONP的请求过程:
- 请求阶段:浏览器创建一个
script
标签,并给其src
赋值(同时在客户端注册一个cb
方法jsonpCallback()
,放到window
对象上;并把callback
的名字(jsonpCallback
)传给服务器:类似http://example.com/api/?cb=jsonpCallback
),再将script
标签插入 DOM。 - 发送请求:当给
script
的src
赋值并插入 DOM 后(不将script
标签插入 DOM ,是不会发起请求的),浏览器就会发起一个请求。 - 数据响应:服务端将要返回的数据作为参数和函数名称拼接在一起(字符串的形式,格式类似
"jsonpCallback({name: 'abc'})"
返回。当浏览器接收到了响应数据,由于发起请求的是script
,所以相当于直接调用jsonpCallback
方法,并且传入了一个参数。(解析script
标签后,会执行jsonpCallback(JOSN)
)。
通过这种方式,即可实现跨域获取数据。
2. 封装一个jsonp工具
function createScript(url) {
let script = document.createElement('script');
script.setAttribute('src', url);
script.setAttribute('type', 'text/javascript');
script.async = true; // 或者直接插入文档底部 document.body.appendChild(script);
return script;
}
// NumberObject.toString(radix) radix数字转化的基数:2~36之间的整数,36表示36进制,能将26个字母全部运用上
// /[^a-z]+/g :[^a-z]表示除a~z之间的字符 +:{1,}表示出现的所有字符
// 字符串的方法slice和substring都表示切割从起始位置到结束位置(不包括)之间的字符 substr()第一个参数表示切割的起始位置 第二个参数表示切割的长度
function gernerateCbName(prefix, num) {
return prefix + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, num);
}
// 我们将所有的callback都设置成了一个全局变量,这样的原因是因为我们需要在数据请求完成之后调用这个方法,因此不得不设置为一个全局变量。
// 但是当我们有多个请求,并且每个请求的处理都是不一样的时候,这个变量将会被覆盖。这是不行的,因此我们应该为每一次请求设置一个唯一且不会冲突的变量
// 页面中若有大量这样的请求之后,window中会含有大量的全局变量,而且还有大量的script标签,这显然不是我们想要的,所以我们要在请求完成之后删除变量和script标签。
function jsonp({ url, params, timerout = 0}) {
let timer = null;
let cbName = gernerateCbName('cb');
let arr = [];
params = { ...params, cbName }
for(let key in params) {
arr.push(`${key}=${params[key]}`)
}
let script = createScript(`${url}?${arr.join('&')}`);
document.getElementsByTagName('head')[0].appendChild(script);
// 错误处理(例如资源加载失败)
script.onerror = function() {
reject(new Error(`fetch ${url} failed`));
window[cbName] = null;
timer && clearTimeout(timer);
document.getElementsByTagName('head')[0].removeChild(script);
}
return new Promise((resolve, reject) => {
window[cbName] = function(data) {
resolve(data);
window[cbName] = null;
timer && clearTimeout(timer);
document.getElementsByTagName('head')[0].removeChild(script);
}
// 超时处理
if(timerout != 0) {
timer = setTimeout(() => {
reject(new Error('TimeOut'));
timer && clearTimeout(timer);
// window[cbName] = null;
// document.getElementsByTagName('head')[0].removeChild(script);
}, timerout);
}
});
}
// 使用
jsonp({
url: 'http://localhost:3000',
params: {
name: 'xql'
},
timerout: 1
}).then((data) => {
console.log(data);
}).catch((err) => {
console.log(err);
});
测试:node server.js 打开index.js 可以看到跨域成功
// server.js
let express = require('express');
let app = express();
app.get('/', function(req, res){
let { cbName } = req.query; // cbName是和前端约定好的字段名称,即服务端和客户端这个字段保持一致
console.log(cbName);
res.end(`${cbName}('hello')`); // `${cbName}(数据 or JSON数据)`
});
app.listen(3000);
3. 注意
- JSONP虽然可以兼容老版的浏览器,只支持GET请求而不支持POST等其它类型的HTTP请求;
- 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
- 容易受xss攻击(安全问题)。在使用JSONP的时候必须要保证使用的JSONP服务必须是安全可信的。万一提供JSONP的服务存在页面注入漏洞,它返回的javascript都将被执行,若被注入这是非常危险的。
- JSONP在调用失败的时候不会返回各种HTTP状态码(解决方法:添加timeout参数,虽然JSONP请求本身的错误没有被捕获,但是最终会因为超时而执行error回调)。
二、CORS(通过前后端http header配置来进行跨域的一种方式)
Cross-Origin Resource Sharing 跨源资源共享
浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
可以把CORS分为:简单请求、复杂请求。
1. 简单请求
- 只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。
- 不会使用自定义请求头(类似于 X-Modified 这种)。
流程:
- 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,自动增加一个Origin字段:表明本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
- 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含
Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com
: 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。Access-Control-Allow-Credentials: true
:该字段可选。它的值是一个布尔值且只有 true 这一个值,表示是否允许发送Cookie,同时开发者必须在AJAX请求中打开withCredentials: true
属性才生效。默认情况下,Cookie不包括在CORS请求之中。
2. 非简单请求
2.1 预检请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,"预检"请求的头信息包括两个特殊字段。Access-Control-Request-Method
:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,例如PUT。Access-Control-Request-Headers
:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,例如X-Custom-Header。
2.2 预检请求的回应
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
Access-Control-Allow-Origin
字段,表示该域名可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。
Access-Control-Allow-Methods
: 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。Access-Control-Allow-Headers
:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。Access-Control-Allow-Credentials
:该字段与简单请求时的含义相同。Access-Control-Max-Age
:该字段可选,用来指定本次预检请求的有效期,单位为秒。例如:有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
2.3 浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。
优点
- 前端方便不少,只需要发请求而不用考虑跨域问题;
- 安全性能够得以控制和保障;
缺点
- 兼容性不全面,需要做降级处理;
使用方式
- 正常请求即可,无论是你要用xhr,还是用一些封装好的组件,如fetch,fetchJsonp,亦或是jquery一类的技术均可;
- 后端在response时需要设置一定的配置参数,并保证安全策略
CORS和JSONP对比
- JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
- 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
- JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。
完全不同源的跨域(两个页面之间的通信)
二、 postMessage
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是 iframe 窗口和window.open方法打开的窗口,它们与父窗口无法通信。
比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。
// 父窗口想获取子窗口的DOM,因为跨源导致报错。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
反之亦然,子窗口获取主窗口的DOM也会报错。
window.parent.document.body
// 报错
如果两个窗口一级域名相同,只是二级域名不同,那么设置document.domain
属性,就可以规避同源政策,拿到DOM。
对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。
- 利用 location.hash + iframe
- 利用 window.name + iframe
- 跨文档通信API: window.postMessage
1 通过修改document.domain来跨子域(只适用于主域相同、不同子域的框架间的交互)
有一个页面,它的地址是 http://www.example.com/a.html
, 在这个页面里面有一个 iframe,它的src是 http://example.com/b.html
, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的。
- 在页面
http://www.example.com/a.html
中设置document.domain
:
<iframe id = "iframe" src="http://example.com/b.html" onload="test()"></iframe>
<script type="text/javascript">
// function test(){
// var iframe = document.getElementById('ifame');
// var win = document.contentWindow;// undefined 可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
// var doc = win.document;//这里获取不到iframe里的document对象
// var name = win.name;//这里同样获取不到window对象的name属性
// console.log(win)
// }
document.domain = 'example.com';//设置成主域
function test(){
alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
}
</script>
- 在页面
http://example.com/b.html
中也设置document.domain
:
<script type="text/javascript">
document.domain = 'example.com';
//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>
2 利用 location.hash + iframe(这种跨域方法主要是通过设置/监听url的hash部分,来实现跨域)
原理:location.hash方式跨域,是子框架能够修改父框架src的hash值,子框架通过修改父框架 src 的 hash 值来传递信息。且更改hash值,页面不会刷新。但是传递的数据的字节数是有限的。
步骤:动态插入一个iframe,将iframe的src属性指向服务端地址。这时top window和包裹这个iframe的子窗口是不能通信的(同源策略),所以改变子窗口的路径就行了,将数据当做改变后的路径的hash值加在路径上,然后就能通信了(和window.name跨域几乎相同),将数据加在index页面地址的hash值上。index页面监听地址的hash值变化(html5有hashchange事件,用setInterval不断轮询判断兼容ie6/7),然后做出判断,处理数据。
假设域名a.com下的文件cs1.html要和jianshu.com域名下的cs2.html传递信息。
1、cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向jianshu.com域名下的cs2.html页面。
2、cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据。
3、同时在cs1.html上加一个定时器,隔一段时间来判断 location.hash的值有没有变化,一旦有变化则获取获取hash值。
注:由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe。
//a页面的代码
<script type="text/javascript">
iframe = document.createElement('iframe');
iframe.style.display = 'none';
var state = 0;
iframe.onload = function() {
if(state === 1) {
var data = window.location.hash;
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: 4000/b.html';
}
};
document.body.appendChild(iframe);
</script>
//b页面代码
<script type="text/javascript">
parent.location.hash = "world";
</script>
优点:1.可以解决域名完全不同的跨域。2.可以实现双向通讯。
缺点:location.hash会直接暴露在URL里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验。另外由于URL大小的限制,支持传递的数据量也不大。有些浏览器不支持 onhashchange 事件,需要轮询来获知URL的变化。
3 利用 window.name + iframe (父框架和子框架的src必须指向同一域名)
原理:window.name(一般在js代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,每个 iframe 都有 window.name 的属性。window.name属性的神奇之处在于一个 iframe 的 name 值在不同的页面(甚至不同域名,即改变了这个iframe的 src 默认情况下,name依然保持不变)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。
window.name = data;//父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入。
location = 'http://parent.url.com/xxx.html';//接着,子窗口跳回一个与主窗口同域的网址。
var data = document.getElementById('myFrame').contentWindow.name。//然后,主窗口就可以读取子窗口的window.name了。
步骤:在iframe载入过程中,迅速重置iframe.src的指向,使之与index.html同源,那么index页面就能去获取到这个 iframe 的name值了(把子框架的 src 改变和父框架同源,而子框架的 name 依然保持不变)!
所以,iframe子框架需要一直不停地刷新,每次触发onload事件后,重置src,相当于重新载入页面,又触发onload事件,于是就不停地刷新了(但是需要的数据还是能输出的);
// index.html
<script type="text/javascript">
iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'http://localhost: 4000/b.html';
document.body.appendChild(iframe);
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: 8000/proxy.html'; // 父框架的域
}
};
</script>
// http://localhost: 4000/b.html文件
<script type="text/javascript">
window.name = "hello";
</script>
优点:window.name容量很大,可以放置非常长的字符串;(2MB)
缺点:必须监听子窗口window.name属性的变化,影响网页性能。
4 window.postMessage
信息传递除了客户端与服务器之前的传递,还存在以下几个问题:
- 页面和新开的窗口的数据交互。
- 多窗口之间的数据交互。
- 页面与所嵌套的iframe之间的信息传递。
上面两种方法都属于破解,再加上iframe用的比较少了,所以这些方法也就有点过时了。window.postMessage是一个HTML5的api,允许两个窗口之间进行跨域发送消息,不论这两个窗口是否同源。这个应该就是以后解决dom跨域通用方法了。
// 语法:data:将要发送到其他 window 的数据; targetOrigin:指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI
otherWindow.postMessage(data, targetOrigin, [transfer]);
父窗口和子窗口都可以通过message事件,监听对方的消息。message事件的事件对象event,提供以下三个属性:
- event.source:发送消息的窗口。
- event.origin: 消息发向的网址。
- event.data:消息内容。
// a.html
<iframe src="http://localhost:4000/b.html" id="frame" onload="load()"></iframe>
<script>
function load(params){
let frame = document.getElementById('frame');
//获取iframe中的窗口,给iframe里嵌入的window发消息
frame.contentWindow.postMessage('hello','http://localhost:4000')
// 接收b.html回过来的消息
window.onmessage = function(e){
console.log(e.data)
}
}
</script>
// b.html
<script>
//监听a.html发来的消息
window.onmessage = function(e){
console.log(e.data)
//给发送源回消息
e.source.postMessage('nice to meet you',e.origin)
}
</script>
/*
* A窗口的域名是<http://example.com:8080>,以下是A窗口的script标签下的代码:
*/
var popup = window.open(...popup details...);
// 如果弹出框没有被阻止且加载完成
// 这行语句没有发送信息出去,即使假设当前页面没有改变location(因为targetOrigin设置不对)
popup.postMessage("The user is 'bob' and the password is 'secret'",
"https://secure.example.net");
// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去(targetOrigin设置对了)
popup.postMessage("hello there!", "http://example.org");
function receiveMessage(event)
{
// 我们能相信信息的发送者吗? (也许这个发送者和我们最初打开的不是同一个页面).
if (event.origin !== "http://example.org")
return;
// event.source 是我们通过window.open打开的弹出页面 popup
// event.data 是 popup发送给当前页面的消息 "hi there yourself! the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);
/*
* 弹出页 popup 域名是<http://example.org>,以下是script标签中的代码:
*/
//当A页面postMessage被调用后,这个function被addEventListenner调用
function receiveMessage(event)
{
// 我们能信任信息来源吗?
if (event.origin !== "http://example.com:8080")
return;
// event.source 就当前弹出页的来源页面
// event.data 是 "hello there!"
// 假设你已经验证了所受到信息的origin (任何时候你都应该这样做), 一个很方便的方式就是把enent.source
// 作为回信的对象,并且把event.origin作为targetOrigin
event.source.postMessage("hi there yourself! the secret response " +
"is: rheeeeet!",
event.origin);
}
window.addEventListener("message", receiveMessage, false);
优点:不需要后端介入就可以非常简单的的做到跨域,一个函数外加两个参数(请求url,发送数据)就可以搞定;移动端兼容性好;
缺点:
- 无法做到一对一的传递方式:监听中需要做很多消息的识别,由于postMessage发出的消息对于同一个页面的不同功能相当于一个广播的过程,该页面的所有onmessage都会收到,所以需要做消息的判断;
- 安全性问题:三方可以通过截获,注入html或者脚本的形式监听到消息,从而能够做到篡改的效果,所以在 postMessag e和 onmessage 中一定要做好这方面的限制;
- 发送的数据会通过结构化克隆算法进行序列化,所以只有满足该算法要求的参数才能够被解析,否则会报错,如function就不能当作参数进行传递;
LocalStorage
文章开头说了,浏览器的同源策略会导致,LocalStorage有同源限制。
解决办法:通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。
References
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论