通信类
- 前后端通信方式
- 如何创建Ajax
- 什么是同源策略?同源策略的目的?非同源的限制范围?
- 解决跨域的几种方式
前后端通信的方式:
- Ajax
- WebScocket
- CROS
如何创建Ajax
原理: 通过XMLHttpRequest对象向服务器发送异步请求,从服务器获得数据,然后操作DOM更新数据。XMLHttpRequest对象是ajax的基础
XMLHttpReques对象的属性有:
-
-
- onreadystatechange: 每当readyState状态改变时,就会触发该事件
-
-
-
- readyState
-
该属性存有XMLHttpRequest的状态。从0到4放生变化:
-
-
-
-
- 0(请求未初始化):XMLHttpRequest对象还没有完成初始化,尚未调用open 方法
- 1(服务器连接已建立):XMLHttpRequest对象开始发送请求。open方法已被调用
- 2(请求已接收):XMLHttpRequest对象请求发送完成。send方法已被调用,header已被接收
- 3(请求处理中):XMLHttpRequest对象开始读取服务器的响应,正在解析接收到的内容。responseText已有部分内容
- 4(请求完成,且响应已就绪):XMLHttpRequest对象读取服务器响应结束。下载完成
-
- status: 从服务器返回的HTTP状态码
-
-
-
-
- responseText:从服务器返回字符串形式的响应数据
-
-
-
- responseXML: 从服务器返回 XML 形式的响应数据
-
封装ajax代码如下:
function ajax(options){ //默认参数 var defaults = { url: '', type: 'GET', data:{}, success: function(){}, error: function(){} }; //覆盖默认参数 for(var key in options){ defaults[key] = options[key]; } if(defaults.url){ var xmlhttp = new XMLHttpRequest() //参数 var dataArr = []; for(var k in data){ dataArr.push(k + '=' + data[k]); } if(defaults.type.toUpperCase() === 'GET'){//GET方法 var url = defaults.url + '?' + dataArr.join('&'); xmlhttp.open(defaults.type,url,true); xmlhttp.send(); //将请求发送到服务器 }else if(defaults.type.toUpperCase() === 'POST'){//POST 方法 xmlhttp.open(defaults.type,defaults.url,true); xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xmlhttp.send(dataArr.join('&')); } xmlhttp.onreadystatechange = function(){ if(xmlhttp.readyState === 4 && (xmlhttp.status === 200 || xmlhttp.status === 304)){ if(defaults.success && defaults.success instanceof Function){ //接收到相应数据 var data = xmlhttp.responseText; defaults.success(data); } }else{ if(defaults.error && defaults.error instanceof Function){ defaults.error(); } } } } }
调用方式:
ajax({ url: '/url', type:'POST', data:{ id: 'id1', name:'name' }, success: function(data){ //成功后执行的代码 console.log('success') }, error: function(status){ //失败后执行的代码 } });
面试题:手写一个简易的ajax请求,要求用promise:
function ajax(url,method='GET'){ return new Peomise((resolve,reject)=>{ const xhr = new XMLHttpRequest() xhr.open(method,url,true) xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ if(xhr.status === 200 || xhr.status === 304){ resolve(JSON.parse(xhr.responseText)) }else{
reject()
} } }
xhr.send() }) }
//用法:
ajax('/data.json','GET').then(res=>{
conssole.log(res)
}).catch(err=>{
console.log(err)
})
同源策略
1.什么是同源策略
先了解一下什么是同源,所谓的“同源”指的是三个相同:
-
-
- 协议相同
- 域名相同
- 端口相同
-
只有这三个都相同才是同源,否则为非同源。
同源策略:指的是浏览器对不同源的脚本或者文本的访问进行的限制
2. 目的
为了保护用户信息安全,防止恶意的网站窃取数据
3.限制范围
非同源之间有如下行为都是受到限制的:
-
- cookie、localStorage无法读取
- DOM 无法获取
- 限制AJAX 请求获取数据(ajax可以发送,但浏览器不会交给开发者)
这些限制是必要的,但是有时候我们合理的用途也受到了影响。下面将介绍,如何避免上面三种限制
4. 注意几点
1)跨域限制只存在浏览器端,服务端不存在跨域
2)即使跨域了,Ajax请求也可以正常发出,但响应数据不会交给开发者
3)<img> 、<link>、<script> 这些标签发出的请求可以是跨域的
-
-
- <img src="跨域的图片地址" />
- <link href="跨域的css地址"/>
- <script src="跨域的js地址"></script>
-
解决跨域的方案
为了解决上面三种情况不再受到限制,我们采用了不同跨域的解决方案
一、Cookie
我们知道只同源的网页Cookie才能共享。那么怎么才能获取到非同源网站的cookie呢?可以通过设置document.domain来共享cookie
方案: 设置document.domain为一级域名
该方案的适用条件:两个网页一级域名相同,只是二级域名不同的情况
举例说明:
网页A (http://btp.example.com/a.html)中设置了一个cookie
document.cookie = "number=1";
网页B (http://test.example.com/b.html)中想获取到网页A中设置的cookie,A和B的主域名相同二级域名不同,是非同源的。此时,我们可以设置document.domain 为一级域名exanple.com,即:
document.domain = 'example.com';
这样,在网页B中就可以读取到网页A中设置的cookie了。
var allCookie = document.cookie; //'number=1'
二、DOM元素获取
适用条件: iframe页面,父窗口和子窗口两个页面的一级域名相同,只是二级域名不同的情况
方案:设置document.domain为一级域名
举例说明:
父页面 A (http://btp.example.com/a.html):
<div id="parent">parent</div> <iframe id="iframe" src="http://test.example.com/b.html" height="200" width="200"></iframe> <script> window.onload = function(){ var _iframe = document.getElementById('iframe').contentWindow; var div = _iframe.document.getElementById('child'); } </script>
子页面 B(http://test.example.com/b.html):
<div id="child">child</div> <script> window.onload = function(){ var _iframe = window.parent; var _div = _iframe.document.getElementById('parent'); } </script>
由于跨域导致浏览器会报错。只需要设置 在页面那中设置 document.domain = 'example.com’,就可以拿到DOM元素了
三、通信
对于主域名相同二级域名不同的情况,我们可以设置document.domain属性来解决跨域通行。那么对于完全不同源的网站,可以使用以下方法来解决:
1. hash
方案:把信息放到URL的#号后面,然后通过监听haschange事件
适用条件:A 通过iframe嵌入(或frame嵌入了跨域的)页面 B
举例说明:
父窗口把数据data写到子窗口url的#号后面:
<div id="parent">parent</div> <iframe id="iframe" src="http://example.com/b.html" height="200" width="200"></iframe> <script>
var B = document.getElementById('iframe').src;
B.src = B.src + '#' + data;
</script>
子窗口监听haschange事件得到通知:
window.onhaschange = function(){ var message = window.location.hash; }
子窗口也同样可以把数据传给父窗口
parent.location.href = parent.location.href + '#' + data
2. postMessage
h5为了解决跨域通信问题,引入了跨域通信API,这个API增加了一个window.postMessage方法,该方法允许跨域通信。
方案:数据通过postMessage方法发送消息,然后通过监听message来获取数据
适用条件:A 通过iframe嵌入(或frame嵌入了跨域)页面 B
举例说明:
父窗口A(http://aa.com/a.html)向子窗口B(http://example.com/b.html)发送消息 : Bwindow.postMessage(message,origin)
<div id="parent">parent</div> <iframe id="iframe" src="http://example.com/b.html" height="200" width="200"></iframe> <script> window.onload = function(){ var data ={ name:'test', age:8 }; document.getElementById('iframe').contentWindow.postMessage(JSON.stringify(data),'http://example.com'); } </script>
注: postMessage的写法,postMessage之前是你要通行的window对象(也就是你要向谁通信),上例子中是向子窗口B发送消息。
postMessage方法中接收两个参数:
* 第一个参数:传递的数据(推荐使用字符串格式)
* 第二个参数:目标窗口的源,协议+主机+端口号。如果设置为“*”,表示可以传递任意窗口
子窗口B接收数据:
window.addEventListener('message',function(event){ console.log(event.source); //发送消息的窗口window对象 A窗口的window console.log(event.origin); //发送消息的窗口的源 http://aa.com console.log(event.data); //消息内容 {name:'test',age:8}; },false);
子窗口通过event.source属性引用父窗口,可以向父窗口发送消息
window.addEventListener('message',function(event){ event.source.postMessage('hello',event.origin) },false);
四、AJAX
AJAX只能发送给同源的网址,否则会报错。解决ajax跨域的方案如下:
1. JSONP(只能发GET请求)
JSONP基本思想:网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策的限制;服务器收到请求后,将数据放到指定名字的回调函数中传回来
具体步骤:
1.动态插入<script>元素,由它向跨源网址发出请求
2. 该请求的url有一个callback参数,用来指定回调函数的名字,该回调函数需要在页面中定义好。
3. 服务器收到请求后,会将数据放到指定的回调函数中作为参数返回。
具体代码实现:
function createScript(src){ var script = document.createElement('script'); script.setAttribute('type','text/javascript'); script.src = src; document.getElementsByTagName('head')[0].appendChild(script); }; window.onload = function(){ var url = 'http://example.com/ip?callback=demo'; createScript(url); }; //定义foo函数 function demo(data){ console.log('success :' + data); }
服务器收到请求后,会将数据放在回调函数demo的参数位置返回
demo({success:true,data:[]})
这样,我们就可以在定义的foo函数中读取到返回的数据了
2. CORS(解决跨域最正统的方式,前提服务器要求是自己人)
1. CROS是一种访问机制,全称“跨域资源共享”(Cross-Origin Resource Sharing),是用于控制浏览器校验跨域请求的一套规范,服务器依照CORS规范,添加特定的响应头来控制浏览器校验,大致如下:
1)服务器明确表示拒绝跨域请求,或没有表示,则浏览器校验不通过
2)服务器明确表示允许跨域请求,则浏览器校验通过
2. CORS 解决跨域的具体实现:
1)CORS 解决简单请求跨域
思路:服务器在给出响应时,通过添加 Access-Control-Allow-Origin 响应头,来明确某个源发起跨域请求。随后浏览器在校验时,直接通过
服务端核心代码(以express 框架为例):
function corsMiddleWare(req,res,next){ // 允许http://127.0.0.1:5500 这个源发起跨域请求 res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500') next() } // 配置路由并使用中间件 app.get('/',corsMiddleware,(req,res)=>{ res.send('hello') })
2)CORS 解决简复杂请求跨域
1. CORS 会把请求分为简单请求和复杂请求
2.预检请求:
3. CORS 解决复杂请求跨域
第一步: 服务器先通过浏览器的预检请求,服务器需要返回如下响应头:
Access-Control-Allow-Origin: 允许的源
Access-Control-Allow-Method: 允许的方法
Access-Control-Allow-Headers: 允许的自定义头
Access-Control-Max-Age: 预检请求的结果缓存时间(可选),在设定的时间内只预检一次,不用每次都预检
第二步:处理实际的跨域请求(与处理简单请求跨域方式相同)
服务端核心代码(nodejs、express):
const express = require('express) const app = express() const students = [ {id:'01',name:'张三',age:18}, {id:'02,name:李四,age:19} ] //处理预检请求 app.options('/students',(req,res)=>{ //设置允许跨域请求源 res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500') //设置允许请求的方法 res.setHeader('Access-Control-Allow-Method','GET') //设置允许的请求头 res.setHeader('Access-Control-Allow-Headers','school,city') //设置预检请求的缓存时间 res.setHeader('Access-Control-Max-Age',7200) //发送响应 res.send() }) //处理实际请求 app.get('/students',(req,res)=>{ //设置跨域请求源 res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500') res.send(students) })
3)借助cors库快速完成配置
自己配置响应头太麻烦,可以借助cors 库更方便完成配置。用法:
1. 安装cors
2. 简单配置cors : app.use(cors())
3. 服务端完整配置如下:
//cors 中间件配置 const corsOptions = { origin:'http://127.0.0.1:5500' //允许的源 methods:['GET','POST',DELETE','HEAD','OPTIONS'] //允许的方法 allowedHeaders:['school','city'], // 允许自定义的头 exposedHeaders:['abc'], // 要暴露的响应头(需要时配) } app.use(cors(corsOptions))
实际项目中ajax的常用插件(工具)
1. jQuery(老项目会用到)
2. fetch
3.axios(推荐)