vlambda博客
学习文章列表

[js进阶]用原生js实现一个ajax?

XMLHttpRequest基本使用方法

都说js是单线程的,那js是怎么实现异步请求的呢?

其实仔细了解了js的运行机制后,才发现,js会专门使用一个子线程来处理请求操作(XMLHttpRequest),只是因为这些线程都是主线程开出来的,是完全听命于主线程的,所以才这么认为js是单线程的(单主线程)。

img

1、readyState:返回当前文档的载入状态

0:(未初始化)还没有调用send()方法

1:(载入)已调用send()方法,正在发送请求

2:(载入完成)send()方法执行完成,已经接收到全部响应内容

3:(交互)正在解析响应内容

4:(完成)响应内容解析完成,可以在客户端调用了

在通过Ajax向服务器发送请求的过程中,XMLHttpRequest对象的状态会发生多次变化。由于 readystatechange 事件是在 xhr 对象状态变化时触发(不单是在得到响应时),也就意味着这个事件会被触发多次。XMLHttpRequest的主要状态事件如下:

var xhr = new XMLHttpRequest()
xhr.open('GET''/time')
xhr.onload = function ({
    // onload readyState => 4
    // 只在请求完成时触发
    console.log(this.readyState)
}
xhr.onprogress = function ({
    // onprogress readyState => 3
    // 只在请求进行中触发
    console.log(this.readyState)
}
xhr.onloadstart = function ({
    // onloadstart readyState => 1
    // 开始发送请求的时候触发
    console.log(this.readyState)
}
xhr.onloadend = function ({
    // onloadend readyState => 4
    // 请求响应过程结束的时候触发
    console.log(this.readyState)
}

xhr.onreadystatechange = function ({
    // 请求响应状态每次改变都会触发  
}
xhr.send(null)

2、status:HTTP状态码

1XX:信息性状态码   ,表示接收的请求正在处理

2XX:成功状态码    , 表示请求正常处理

3XX:重定向状态码  ,表示需要附加操作来完成请求

4XX:客户端错误状态 ,表示服务器无法处理请求

5XX:服务器错误状态 ,表示服务器处理请求出错

3、 get和post

GET请求的参数需要放在queryString中

POST的请求数据一般是放在formData中(form表单),当然,还有其他的方式,比如payload中的json等

4、同步请求和异步请求

当async属性的值为false时是同步的,Ajax请求将整个浏览器锁死,只有ajax请求返回结果后,才执行ajax后面的alert语句。

当async属性的值为true时是异步的,即不会等待ajax请求返回的结果,会直接执行ajax后面的alert语句。

实现一个简单的ajax

  • 支持get、post两种请求方式

  • 支持同步、异步

  • 支持自定义传data的格式(get默认为queryString、post默认为formData)

  • 支持超时机制

  • 兼容ie6等早期版本

其中一个ajax的使用示例如下:

$.ajax({
    type: "GET",
    url: "www.baidu.com/s",
    data: { wd: "test" },
    contentType: "application/json;charset=utf-8",
    dataType: "json",  // 传回来的数据格式
    success: function (data) {
        // 更新视图
        // 关闭加载动画等操作
    }
});

contentType: 告诉服务器,我要发什么类型的数据

dataType:告诉服务器,我要想什么类型的数据,如果没有指定,那么会自动推断是返回 XML,还是JSON,还是script,还是String。

思路

首先用伪码来描述一下大致逻辑

// TODO: 将字典(obj键值对)转化为拼接queryString的函数方法。
function formatParams(data{
   ...
   return formatParams
}


// TODO: 创建一个兼容性高的xhr
function createXMLHttpRequest(){
    ...
    return xhr
}

// 根据options进行同步或异步请求
function myAjax(options){
    读取options配置
   
    xhr = createXMLHttpRequest()
    
    // 发送请求部分
    if(是get请求){
        // 这里的options.async为true或false  true为异步 false为同步
        xhr.open("GET", options.url + "?" + formatParams(options.data), options.async);
    }else if(是post请求){
        xhr.open("POST", options.url, options.async);
        
        contentType = options.contentType || "application/x-www-form-urlencoded"
        xhr.setRequestHeader("Content-type", contentType);
        xhr.send(params);
    }
    
    // 设置超时放弃请求机制
    setTimeout(function ({
        if(xhr状态未变成完成请求状态){
            // 中断请求
            xhr.abort();
        }
    }, options.timeout)
    
    // 请求完成后执行的操作 执行用户传入的success  fail钩子函数
     xhr.onreadystatechange = function ({
            if (xhr状态变成完成了请求状态) {
                if (状态码成功) {
                 执行成功的钩子函数
                } else {
                    执行失败的钩子函数
                }
            }

        }
}

实现

浏览器兼容判断

function createXMLHttpRequest({
        var xhr;
        // 适用于大多数浏览器,以及IE7和IE更高版本
        try{
            xhr = new XMLHttpRequest();
        } catch (e) {
            // 适用于IE6
            try {
                xhr = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                // 适用于IE5.5,以及IE更早版本
                try{
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e){}
            }
        }            
        return xhr;
    }

格式化请求参数

// TODO: 格式化请求参数
function formatParams(data{
    var arr = [];
    for (var key in data) {
        arr.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
    }
    return arr.join("&");
}
[js进阶]用原生js实现一个ajax?
img

myAjax包装

function myAjax(options{
    //调用函数时如果options没有指定,就给它赋值{},一个空的Object
    options = options || {};

    // 请求格式GET、POST,默认为GET
    options.type = (options.type || "GET").toUpperCase();

    //响应数据格式,默认json
    options.dataType = options.dataType || "json";

    //Post请求时data的格式,默认为formData
    options.contentType = options.contentType || "application/x-www-form-urlencoded";

    // 同步请求还是异步请求  true为异步请求async 默认true
    options.async = options.async || true;

    // 默认超时时间 3分钟
    options.timeout = options.timeout || (1000 * 60 * 3)

    // 。。。。其它配置项暂时不扩展

    var xhr;

    xhr = new XMLHttpRequest();



    //启动并发送一个请求
    if (options.type == "GET") {
        xhr.open("GET", options.url + "?" + formatParams(options.data), options.async);
        xhr.send(null);
    } else if (options.type == "POST") {
        xhr.open("POST", options.url, options.async);

        // 设置请求头
        xhr.setRequestHeader("Content-Type", options.contentType);

        // 判断请求体
        if (options.contentType == "application/x-www-form-urlencoded") {
            xhr.send(formatParams(options.data));
        } else {
            xhr.send(options.data);
        }
    }

    //  设置有效时间
    setTimeout(function ({
        if (xhr.readySate != 4) {
            xhr.abort();
        }
    }, options.timeout)

    // 接收
    // options.success成功之后的回调函数  options.error失败后的回调函数    
    //xhr.responseText,xhr.responseXML  获得字符串形式的响应数据或者XML形式的响应数据
    xhr.onreadystatechange = function ({
        if (xhr.readyState == 4) {
            var status = xhr.status;
            if (status >= 200 && status < 300 || status == 304) {
                options.success && options.success(xhr.responseText, xhr.responseXML);
            } else {
                options.error && options.error(status);
            }
        }
    }
}



完整代码(含实验)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title></title>
    <style>
        main {
            width900px;
            height900px;
            border1px red solid;
            margin0 auto;
            position: relative
        }

        main div {
            width200px;
            height200px;
            border1px red solid;
            position: absolute;
            left0;
            right0;
            top0;
            bottom0;
            margin-left: auto;
            margin-right: auto;
            margin-top: auto;
            margin-bottom: auto;
        }
    
</style>
</head>

<body>
    <main>
        <div>
            <button id="id_button_01">点我发送请求(jquery封装的ajax)</button>
            <button id="id_button_02">点我发送请求(自己封装的ajax)</button>

        </div>
        <span>发起请求次数(jquery封装的ajax):<p id="id_text_01"></p></span>
        <span>完成请求次数(jquery封装的ajax):<p id="id_text_02"></p></span>
        <span>发起请求次数(自己封装的ajax):<p id="id_text_03"></p></span>
        <span>完成请求次数(自己封装的ajax):<p id="id_text_04"></p></span>
    </main>
</body>
<script src="https://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script>
<script>
    var counter01 = counter02 = counter03 = counter04 = 0;
    var eText01 = document.getElementById("id_text_01");
    var eText02 = document.getElementById("id_text_02");
    var eText03 = document.getElementById("id_text_03");
    var eText04 = document.getElementById("id_text_04");

    var eButton01 = document.getElementById("id_button_01");
    var eButton02 = document.getElementById("id_button_02");



    //TODO: 格式化请求参数
    function formatParams(data{
        var arr = [];
        for (var key in data) {
            arr.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
        }
        return arr.join("&");
    }

    // TODO: 创建一个兼容性高的xhr
    function createXMLHttpRequest({
        var xhr;
        // 适用于大多数浏览器,以及IE7和IE更高版本
        try {
            xhr = new XMLHttpRequest();
        } catch (e) {
            // 适用于IE6
            try {
                xhr = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                // 适用于IE5.5,以及IE更早版本
                try {
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) { }
            }
        }
        return xhr;
    }


    function myAjax(options{
        //调用函数时如果options没有指定,就给它赋值{},一个空的Object
        options = options || {};

        // 请求格式GET、POST,默认为GET
        options.type = (options.type || "GET").toUpperCase();

        //响应数据格式,默认json
        options.dataType = options.dataType || "json";

        //Post请求时data的格式,默认为formData
        options.contentType = options.contentType || "application/x-www-form-urlencoded";

        // 同步请求还是异步请求  true为异步请求async 默认true
        options.async = options.async || true;

        // 默认超时时间 3分钟
        options.timeout = options.timeout || (1000 * 60 * 3)

        // 。。。。其它配置项暂时不扩展

        var xhr;

        xhr = new XMLHttpRequest();



        //启动并发送一个请求
        if (options.type == "GET") {
            xhr.open("GET", options.url + "?" + formatParams(options.data), options.async);
            xhr.send(null);
        } else if (options.type == "POST") {
            xhr.open("POST", options.url, options.async);

            // 设置请求头
            xhr.setRequestHeader("Content-Type", options.contentType);

            // 判断请求体
            if (options.contentType == "application/x-www-form-urlencoded") {
                xhr.send(formatParams(options.data));
            } else {
                xhr.send(options.data);
            }
        }

        //  设置有效时间
        setTimeout(function ({
            if (xhr.readySate != 4) {
                xhr.abort();
            }
        }, options.timeout)

        // 接收
        // options.success成功之后的回调函数  options.error失败后的回调函数    
        //xhr.responseText,xhr.responseXML  获得字符串形式的响应数据或者XML形式的响应数据
        xhr.onreadystatechange = function ({
            if (xhr.readyState == 4) {
                var status = xhr.status;
                if (status >= 200 && status < 300 || status == 304) {
                    options.success && options.success(xhr.responseText, xhr.responseXML);
                } else {
                    options.error && options.error(status);
                }
            }
        }
    }


    eButton01.onclick = function ({
        console.log("发起了一个请求");
        counter01++;
        eText01.innerHTML = counter01;

        $.ajax({
            url"test.txt",
            type'get',
            data: {
                wd'特朗普',
            },
            dataType'html',
            timeout50000,
            successfunction (res{
                console.log("发送请求成功");
                console.log(res);
                counter02++;
                eText02.innerHTML = counter02;
            },
            errorfunction (e{
                //异常处理
                console.log(e);
            }
        })

    }
    eButton02.onclick = function ({
        console.log("发起了一个请求");
        counter03++;
        eText03.innerHTML = counter03;
        myAjax({
            url"test.txt",
            type"get",
            data: {
                wd'特朗普',
            },
            dataType'html',
            timeout50000,
            successfunction (res{
                console.log("发送请求成功");
                console.log(res);
                counter04++;
                eText04.innerHTML = counter04;
            },
            errorfunction (e{
                //异常处理
                console.log(e);
            }
        })
    }

</script>

</html>

结果

[js进阶]用原生js实现一个ajax?
img
img
img

可以看到实现得很成功。

结语

之后我将继续深入研究用原生js实现promise。

参考资料

https://www.w3school.com.cn/ajax/ajax_xmlhttprequest_create.asp  w3school  xmlhttprequest

https://www.w3school.com.cn/jquery/ajax_ajax.asp  jQuery ajax - ajax() 方法

https://www.cnblogs.com/qing-5/p/11368009.html [原生js实现ajax封装]

https://www.cnblogs.com/colima/p/5339227.html    [原生js实现Ajax]

https://www.jb51.net/article/119605.htm  深入讲解xhr(XMLHttpRequest)/jsonp请求之abort

《JavaScript权威指南》