1、同源策略
限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。(来自MDN官方的解释)
具体解释:
(1)源
包括三个部分:协议、域名、端口(http协议的默认端口是80)。如果有任何一个部分不同,则源
不同,那就是跨域了。
(2)限制
:这个源的文档没有权利去操作另一个源的文档。这个限制体现在:(要记住)
-
Cookie、LocalStorage和IndexDB无法获取。
-
无法获取和操作DOM。
-
不能发送Ajax请求。我们要注意,Ajax只适合同源的通信
2、前后端常见的通信方式
- Ajax:不支持跨域
- WebSocket:不受同源策略的限制,支持跨域
- CORS:不受同源策略的限制,支持跨域
3、如何创建Ajax
- 创建XMLHttpRequest 对象。(兼容性处理)
- 使用open方法设置请求的参数。open(method, url, 是否异步)。
- 发送请求。
- 注册事件。 注册onreadystatechange事件,状态改变时就会调用。
- 如果要在数据完整请求回来的时候才调用,我们需要手动写一些判断的逻辑。
- 获取返回的数据,更新UI。
onreadystatechange 事件
注册 onreadystatechange 事件后,每当 readyState 属性改变时,就会调用 onreadystatechange 函数。
readyState:(存有 XMLHttpRequest 的状态。从 0 到 4 发生变化)
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
同源Ajax通信和jsonp跨域通信utils.js封装(借用jQuery的思想)
function myAjax(option) {
if(option.dataType === 'jsonp'){ //jsonp跨域请求
myAjax2(option)
}else{ //同源请求
myAjax1(option)
}
}
function myAjax1(option) { //同源请求
var defaultObj = {
type: 'get',
data: {},
async: true,//默认异步加载数据
url: '#',
dataType: 'json',//返回结果的类型
success: function (data) {},
error:function () {
alert('请求失败')
}
}
for (let key in option) {
defaultObj[key] = option[key]
}
var xhr = null
if (XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject()
}
var params = ''
if (JSON.stringify(defaultObj.data) !== '{}') {//如果有参数
params += '?'
for (let i in defaultObj.data) {
params += i + '=' + defaultObj.data[i] + '&'
}
//去掉最后一个&
params = params.substring(0, defaultObj.params);
}
if (defaultObj.type === 'get') {
defaultObj.url += params
xhr.open(defaultObj.type, defaultObj.url, defaultObj.async)
xhr.send(null)
} else {
xhr.open(defaultObj.type, defaultObj.url, defaultObj.async)
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded")
xhr.send(params)
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 304) {
let result = ''
if (defaultObj.dataType === 'xml') {
result = xhr.responseXML
} else if (defaultObj.dataType === 'json') {
result = JSON.parse(xhr.responseText)
} else {
result = xhr.responseText
}
defaultObj.success(result)
} else {
//失败
defaultObj.error()
}
}
}
}
function myAjax2(option) {//跨域请求
var defaultObj = {
data: {},
async: true,//默认异步加载数据
url: '#',
jsonpCallback: 'luo', //自定义的回调函数名称
jsonp: 'callback', //后台接收回调函数的key
success: function (data) {
}
}
for (let key in option) {
defaultObj[key] = option[key]
}
var params = ''
if (JSON.stringify(defaultObj.data) !== '{}') {//如果有参数
params += '?'
for (let i in defaultObj.data) {
params += i + '=' + defaultObj.data[i] + '&'
}
defaultObj.url += params
defaultObj.url += defaultObj.jsonp+'='+defaultObj.jsonpCallback
}else {
defaultObj.url += '?' + defaultObj.jsonp+'='+defaultObj.jsonpCallback
}
var script = document.createElement('script')
script.src = defaultObj.url
window[defaultObj.jsonpCallback] = function(result){
defaultObj.success(result)
}
var head = document.querySelector('head')
head.appendChild(script)
}
4、跨域通信的几种方式
- (1)JSONP
- (2)WebSocket
- (3)CORS
- (4)Hash
- (5)postMessage
(1)JSONP
原理:通过<script>
标签的异步加载来实现的。利用script标签不受同源策略的限制,天然可以跨域的特性。比如说,实际开发中,我们发现,head标签里,可以通过<script>
标签的src,里面放url,加载很多在线的插件。这就是用到了JSONP。
<script>
var util = {};
//定义方法:动态创建 script 标签
/**
* [function 在页面中注入js脚本]
* @param {[type]} url [description]
* @param {[type]} charset [description]
* @return {[type]} [description]
*/
util.createScript = function (url, charset) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
charset && script.setAttribute('charset', charset);
script.setAttribute('src', url);
script.async = true;
return script;
};
/**
* [function 处理jsonp]
* @param {[type]} url [description]
* @param {[type]} onsucess [description]
* @param {[type]} onerror [description]
* @param {[type]} charset [description]
* @return {[type]} [description]
*/
util.jsonp = function (url, onsuccess, onerror, charset) {
var callbackName = util.getName('tt_player'); //事先约定好的 函数名
window[callbackName] = function () { //根据回调名称注册一个全局的函数
if (onsuccess && util.isFunction(onsuccess)) {
onsuccess(arguments[0]);
}
};
var script = util.createScript(url + '&callback=' + callbackName, charset); //动态创建一个script标签
script.onload = script.onreadystatechange = function () { //监听加载成功的事件,获取数据
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = script.onreadystatechange = null;
// 移除该script的 DOM 对象
if (script.parentNode) {
script.parentNode.removeChild(script);
}
// 删除函数或变量
window[callbackName] = null; //最后不要忘了删除
}
};
script.onerror = function () {
if (onerror && util.isFunction(onerror)) {
onerror();
}
};
document.getElementsByTagName('head')[0].appendChild(script); //往html中增加这个标签,目的是把请求发送出去
};
</script>
(2)WebSocket
var ws = new WebSocket('wss://echo.websocket.org'); //创建WebSocket的对象。参数可以是 ws 或 wss,后者表示加密。
//把请求发出去
ws.onopen = function (evt) {
console.log('Connection open ...');
ws.send('Hello WebSockets!');
};
//对方发消息过来时,我接收
ws.onmessage = function (evt) {
console.log('Received Message: ', evt.data);
ws.close();
};
//关闭连接
ws.onclose = function (evt) {
console.log('Connection closed.');
};
(3)CORS
CORS 可以理解成是既可以同步、也可以异步*的Ajax。(CORS为什么支持跨域? 跨域时,浏览器会拦截Ajax请求,并在http头中加Origin。)
fetch 是一个比较新的API,用来实现CORS通信。用法如下:
// url(必选),options(可选)
fetch('/some/url/', {
method: 'get',
}).then(function (response) { //类似于 ES6中的promise
}).catch(function (err) {
// 出错了,等价于 then 的第二个参数,但这样更好用更直观
});
(4)Hash
url的#
后面的内容就叫Hash。Hash的改变,页面不会刷新。这就是用 Hash 做跨域通信的基本原理。
补充:url的?
后面的内容叫Search。Search的改变,会导致页面刷新,因此不能做跨域通信。
使用举例:
场景:我的页面 A 通过iframe或frame嵌入了跨域的页面 B。
现在,我这个A页面想给B页面发消息,怎么操作呢?
(1)首先,在我的A页面中:
//伪代码
var B = document.getElementsByTagName('iframe');
B.src = B.src + '#' + 'jsonString'; //我们可以把JS 对象,通过 JSON.stringify()方法转成 json字符串,发给 B
(2)然后,在B页面中:
// B中的伪代码
window.onhashchange = function () { //通过onhashchange方法监听,url中的 hash 是否发生变化
var data = window.location.hash;
};
(5)postMessage
H5中新增的postMessage()方法,可以用来做跨域通信。既然是H5中新增的,那就一定要提到。
场景:窗口 A (http:A.com
)向跨域的窗口 B (http:B.com
)发送信息。步骤如下。
(1)在A窗口中操作如下:向B窗口发送数据:
// 窗口A(http:A.com)向跨域的窗口B(http:B.com)发送信息
Bwindow.postMessage('data', 'http://B.com'); //这里强调的是B窗口里的window对象
(2)在B窗口中操作如下:
// 在窗口B中监听 message 事件
Awindow.addEventListener('message', function (event) { //这里强调的是A窗口里的window对象
console.log(event.origin); //获取 :url。这里指:http://A.com
console.log(event.source); //获取:A window对象
console.log(event.data); //获取传过来的数据
}, false);