从AJAX到跨域

408 阅读5分钟

最近忙着面试,在面试过程中发现了自己怎么突然对ajax这些http请求有点生疏,赶紧补一课,记录一下。

一:XMLHttpRequest

什么是Ajax? 这是一个可以向服务器请求额外的数据而不需要卸载页面的技术。

Ajax的核心内容就是XMLHttpRequest对象。通过XMLHttpRequest发送一次请求需要哪些步骤?

var xhr = new XMLHttpRequest();  // new 一个XMLHttpRequest对象用于创建连接使用

xhr.open(type, url, boolean);  

type: 请求的方法get/ post/ put ...

url: 请求的地址

boolean: 布尔值,true为异步, false为同步。

在open方法中,只是等待建立的过程,并没有发送请求。

xhr.send(data);   // 开始请求,send接受内容。如果不需要传值设置为null

xhr.readyState属性:用来判断请求进行的步骤

xhr.readyState === 0   // 尚未调用open
xhr.readyState === 1    // open调用, send没有
xhr.readyState === 2   // send调用,没有收到响应
xhr.readyState === 3   // 收到响应,响应没有完全
xhr.readyState === 4   // 响应完全

只要readyState的属性变化,就会触发readystatechange事件.

xhr.status属性: http状态码。 200 成功, 302协商缓存等。

ok.手写一个简单的ajax:

function myAjax (type, url, data) {
    var xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject();
    let newData = '';
    // 处理一下data
    if (typeof data === 'Object') {
        let str = '';
        for (let key in data) {
           if (Object.prototype.hasOwnProperty.call(data, key)) {
                str += `${key}=${data[key}&`;
            }       
        };
       newData = str.slice(0, str.length - 1);
    }
    if(type.toUpperCase() == 'GET') {
        if (newData) {
            xhr.open("GET", `${url}?${newData}`, true);
        } else {
            xhr.open("GET", url, ture);
        };
        xhr.send(null);
    }
    else if (type.toUpperCase() == 'POST') {
        xhr.open("POST", url, true);
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        if (data) {
            xhr.send(data);
        } else {
            xhr.sennd(null);
        }
    };
    xhr.onreadyStatechange = () => {
        if (xhr.reayState === 4 && xhr.status === 200) {
            const request = xhr.responseText;
        } else {
            // error 处理
        }
    }

简单的实现了一下post和get方法。

需要判断一下浏览器,调用不同的创建xhr方法,在ie下可以使用ActiveXObject()方法.

由于get请求需要把参数拼接到url上,这里需要遍历一下data中的key,然后拿到value,拼接到url上。

类似于: baidu.com/lsp?name=lk…

在post请求中不需要,直接可以通过send方法传data。

在onreadystatechange方法中通过xhr.status和xhr.readystate 判断结果。

二: XHR的其他属性介绍

鉴于 XHR 已经得到广泛接受,成为了事实标准,W3C 也着手制定相应的标准以规范其行为。

XMLHttpRequest 1 级只是把已有的 XHR 对象的实现细节描述了出来。而 XMLHttpRequest 2 级则进一步发展了 XHR。

(1): formData类型

var form = document.getElementById("user-info");

xhr.send(new FormData(form)); 

使用 FormData 的方便之处体现在不必明确地在 XHR 对象上设置请求头部。XHR 对象能够识别传

入的数据类型是 FormData 的实例,并配置适当的头部信息。

(2): 超时设定

timeout: 设置请求超时的时间,如果超过这个时间就会停止请求,触发ontimeout方法

xhr.timeout  = 1000;
xhr.ontimeout = () => {
    alert('error');
}

(3): 其他

1. 进度事件:

这些事件最早其实只针对 XHR 操作,但目前也被其他 API 借鉴。有以下 6 个进度事件。

loadstart  // 接受数据第一个字节开始触发
progress     // 在接收响应期间持续不断地触发
error       // 发生错误时触发
abort        // 调用about方法触发
load          // 在接收到完整的响应数据时触发
loadend         // 在通信完成或者触发 error、abort 或 load 事件后触发

三: CORS

通过 XHR 实现 Ajax 通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR 对象只能访问与包含它的页面位于同一个域中的资源。这种安全策略可以预防某些恶意行为。但是,实现合理的跨域请求对开发某些浏览器应用程序也是至关重要的。

1:XDR

XDR其实和XHR差不多,就是在设计的时候考虑到了安全性问题。

xdr:不会携带cookies,头部只能设置content-type字段, 不能访问头部信息, 只能支持get和post。

XDR 对象的安全机制部分实现了 W3C 的 CORS 规范。

2:简单请求和非简单请求

什么是简单请求? 

请求方法只能是post和get,请求头部:

content-type: application/x-www-form-urlencoded / text/plain / multipart/form-data

除了简单请求其他的都是非简单请求。

非简单请求的cors请求,是发送请求之后先在服务端 "预检" 一遍,预检发送的请求方法是 OPTIONS ,看看服务端是否允许跨域。

非简单请求的步骤:

cors 有一个预检测(向服务器发送一个Prefight请求)就是options预检

origin: ''    // 域名来源
Access-Control-Request-Methods: ''   // 自身请求的方法
Acdess-Control-Request-Headers: ''    // 可选  定义头部

服务器端决定是否允许请求:

 *** 通过一下响应字段返回给浏览器 
    * Access-Control-Allow-Origin: '' 
    * Access-Control-Allow-Methods: 允许请求的方法 
    * Access-Control-Allow-Headers: 允许请求头部 
    * Access-Control-Max-Age: 请求缓存多长时间

3: 带凭证的请求

默认情况下,跨源请求不提供凭据(cookie、HTTP 认证及客户端 SSL 证明 等 )。

withCredentials:true 可以指定某个请求应该发送凭据

服务器端: 会响应一下http头部:

Access-Control-Allow-Credentials: true 

四: 其他跨域技术

1: img标签

图像 Ping 是与服务器进行简单、单向的跨域通信的一种方式。请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或 204 响应。通过图像 Ping,浏览器得不到任何具体的数据,但通过侦听 load 和 error 事件,它能知道响应是什么时候接收到的。

let img = new Image();
img.onload = () => {    
    console.log('加载成功');
}
img.onerror = ()=> {    
    console.log('加载失败');
}

经常容易忘!

2: JSONP

什么是jsonp?

jsonp是包含在函数中的json  例如: callback(json);

一个常见的jsonp请求的格式:

http://mroal.zip/json/callback=handleFunc

原理就是script标签不受同源协议限制,在script标签上url加入jsonp的地址.

实现一个jsonp:

const handleFunc = (url, params, callBackNA) {
    // 处理params
    const getUrl = () => {
        let dataSrc = '';
        for (let key in params) {
            if (Object.prototype.hasOwnProperty.call(params, key)) {
                dataSrc += `${key}=${params[key]}&`;
            }
        };
        dataSrc += `callback=${callBackNA}`;
        return `${url}?${dataSrc}`;

    };
    return new Promise((resolve, reject) => {
        const scriptEle = document.createElement('script');
        scriptEle.src = getUrl();
        document.body.appendChild(scriptEle);
        window[callBackNA] = data => {
            resolve(data);
            document.removeChild(scriptEle);
        }
}

通过promise来实现异步。初始化的时候定义一个函数处理params,然后动态创建一个script标签,把url赋值给创建新的script的url上。最后在调用callBackNA中移除标签。同时获取data,传入resolve中。

大概就到这里了,其实还有其他的一些跨域的解决方法。比如nignx代理。这里就不多说了,主要是介绍一下jsonp和ajax。