通信类

  • 前后端通信方式
  • 如何创建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(推荐)     

posted @ 2018-04-11 21:05  yangkangkang  阅读(246)  评论(0编辑  收藏  举报