资讯专栏INFORMATION COLUMN

刨根问底ajax原理与封装

caige / 1783人阅读

摘要:所以今天我们就详细了解一下的实现原理和封装的关键步骤。接下来,我们尝试自己封装一个支持和的简易请求。测试调用小结其实实现原理并不复杂,复杂的是容错兼容性等的处理,使用的时候尽量使用库或者框架提供的封装,避免不必要的漏洞。

说起ajax,大家都不陌生。但是由于目前很多框架或者库等都对网络请求做了封装,导致了很多初学者只知其然而不知其所以然。所以今天我们就详细了解一下ajax的实现原理和封装ajax的关键步骤。

ajax的核心是XMLHttpRequest对象。首先我们先创建一个XMLHTTPRequest对象 var xhr = new XMLHttpRequest();

注意:本文所提及的内容不兼容古老的IE,有想了解的同学自行查阅ActiveXObject相关内容。
XMLHttpRequest

在使用XMLHttpRequest对象的第一步,我们首先要调用open方法来初始化请求参数,xhr.open("get","/test",true),虽然名字叫open,但是此时请求还并没有发送。

open(method, url[, async, username, password])

method:请求类型,例如GET,POST等

url:请求地址(这里有同源限制,就是我们经常会看到的跨域问题啦)

async:是否发送异步请求。可选参数,默认为true。

username&password:可选参数,授权验证使用的,但是我们一般不这么用,使用后请求变成这个样子了,http(s)://username:password@url。

如果调用了open方法后再次对它进行调用,则相当于调用了abort方法,abort方法我们在后面介绍。

如果我们想为为请求绑定一些操作,这个时候就可以开始啦。常用的操作有如下几个:

setRequestHeader(key, value)

顾名思义,这个方法用于设置请求头内容。

key:要设置的请求头名称

value:对应请求头的值

overrideMimeType(type)

重写服务器返回的MIME类型。通过这个方法可以告诉服务器你想要的数据类型。

注意:以上这些操作必须定义在send方法之前。否则,就拿setRequestHeader来说,你都把请求发出去了再设置还有什么用?

这个时候,我们就可以通过调用send 方法来发送请求了,xhr.send(null)

send(data)

发送请求,如果是同步请求的话,会阻塞代码的执行,直至收到服务器响应才会继续。

data:发送给服务器的数据。为了兼容不同的浏览器,即使是不需要传数据,也需要传入参数null。

readyStateChanhe()

每次readyState的值改变的时候都会触发这个函数。

getResponseHeader(name)

获取指定响应头部的值,参数是响应头部的名称,并且不区分大小写。

getAllResponseHeaders()

获取服务器发送的所有HTTP响应的头部。

在这里我们穿插几个概念,readyState,这个属性表明了请求的状态,伴随HTTP请求的整个生命周期,它的值表明此时请求所处的阶段,具体如下:

readyState

数值 描述
0 初始化,open()尚未调用
1 open()已经调用,但是send未调用
2 已获取到返回头信息
3 正在下载返回体信息
4 请求完成

还有几个较为常用的属性

名称 含义
responseText 响应的文本
status 响应的状态码
statusText 响应的状态信息
responseXML 响应内容是“text/xml”或者是“application/xml”格式的时候,这个属性的值就是响应数据的XMLDOM文档。

我们用下面这段代码做个测试

var xhr = new XMLHttpRequest();
console.log(xhr.readyState)
xhr.onreadystatechange = function(){
    console.log("------")
    console.log("readyState:" + xhr.readyState)
    console.log("ResponseHeaders:" + xhr.getAllResponseHeaders())
    console.log("ResponseText:" + xhr.responseText.length)
    console.log("------")
}
xhr.open("get","/")
xhr.send(null)

下图我们可以直观的看到在创建了XMLHttpRequest对象的时候,readyState的值为0。


然后我们定义了onreadystatechange函数,让其打印一些属性,并调用open方法,此时readyState变为1。


最后我们调用send方法,可以看到经历了如下过程:

send方法调用之后,readyState变为2,此时responseHeader已经获取到了,responseText为空;

响应数据开始下载,readyState变为3

响应数据下载结束,readyState变为4.我们可以发现此时responseText的长度比之前长。

abort()

取消响应,调用这个方法会终止已发送的请求。我们尝试在之前的代码最后加一句。

xhr.abort();
console.log(xhr.readyState);


也就是说,send执行以后,并没有去尝试请求数据,而是直接取消掉了,并且我们发现abort会将readyState的值置为0。

除此之外,XMLHttpRequest还有一个很重要的属性withCredentials,cookie在同域请求的时候,会被自动携带在请求头中,但是跨域请求则不会,除非把withCredentials的值设为true(默认为false)。同时需要在服务端的响应头部中设置Access-Control-Allow-Credentials:true。不仅如此Access-Control-Allow-Origin的值也必须为当前页面的域名。

封装

到此为止,我们终于讲完了XMLHttpRequest的一些常用概念。接下来,我们尝试自己封装一个支持get和post的简易jax请求。

function ajax(url, option){
    option = option || {};
    var method = (option.method || "GET").toUpperCase(),
        async = option.async === undefined ? true : option.async,
        params = handleParams(option.data);
    var xhr = new XMLHttpRequest();
    if(async){
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4){
               callback(option,xhr);
            }
        };
    }
    if (method === "GET"){
        xhr.open("GET",url + "?" + params, async);
        xhr.send(null)
    }else if (method === "POST"){
        xhr.open("POST", url, async);
        xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
        xhr.send(params);
    }
    if(!async){
        callback(option,xhr);
    }
    function callback(opt,obj){
        var status = obj.status;
        if (status >= 200 && status < 300 ){
            opt.success && opt.success(obj.responseText,obj.responseXML);
        }else{
            opt.fail && opt.fail(status);
        }
    }
    function handleParams(data) {  
        var arr = [];
        for (var i in data) {
            arr.push(encodeURIComponent(i) + "=" + encodeURIComponent(data[i]));
        }
        return arr.join("&");
    }
}
//  测试调用
ajax("/xxx",{
    method:"POST",
    data:{
        key: "test"
    },
    success:function(){
        console.log("success")
    },
    fail:function(){
        console.log("fail")
    }
});
小结

其实ajax实现原理并不复杂,复杂的是容错、兼容性等的处理,使用的时候尽量使用库或者框架提供的封装,避免不必要的漏洞。

补充

感谢@虾哔哔的提问,这里做个简单的补充说明。

async是一个可选的布尔值参数,默认为true,意味着是否执行异步操作,如果值为false,则send()方法不会返回任何东西,直到接受到了服务器的返回数据。如果为值为true,一个对开发者透明的通知会发送到相关的事件监听者。这个值必须是true,如果multipart 属性是true,否则将会出现一个意外。

根据我的分析,当async为false的时候,readyState会在send方法之后直接由1变成4。也就是说异步模式,send方法会立刻返回。同步模式下,只有响应完全接受后,send才会返回。

另外,由于同步模式会阻塞,较新版本的Chrome在主线程上的同步请求已被弃用。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95877.html

相关文章

  • 两年了,我写了这些干货!

    摘要:开公众号差不多两年了,有不少原创教程,当原创越来越多时,大家搜索起来就很不方便,因此做了一个索引帮助大家快速找到需要的文章系列处理登录请求前后端分离一使用完美处理权限问题前后端分离二使用完美处理权限问题前后端分离三中密码加盐与中异常统一处理 开公众号差不多两年了,有不少原创教程,当原创越来越多时,大家搜索起来就很不方便,因此做了一个索引帮助大家快速找到需要的文章! Spring Boo...

    huayeluoliuhen 评论0 收藏0
  • [vuejs 踩坑实战系列] 路由场景下父子组件的生命周期顺序来个刨根问底

    摘要:大家中秋假期快乐,假期分享一些原理设计文章给大家原创不易,欢迎转发,一起学习凌晨写的,不容易哈,收藏或者点个赞吧在常见的单页应用中,我们都会有一个根文件,里面放置一个然后配置路由来切换很多人在子父组件嵌套关系下的生命周期钩子函数如何应用, 大家中秋假期快乐,假期分享一些原理设计文章给大家 原创不易,欢迎转发,一起学习(凌晨写的,不容易哈,收藏或者点个赞吧) 在常见的单页应用中,我们都...

    FreeZinG 评论0 收藏0
  • mybatis-plus源码分析之sql注入器

    摘要:下面我会详细地从源码的角度分析下文简写成是如何实现自动注入的原理。文件解析器,解析对应的文件信息,并将文件信息注册到中。节点解析器,用于构建节点信息。注册与绑定类,将的类信息与绑定。 微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。 老司机倾囊相授,带你一路进阶,来不及解释了快上车! mybatis-plus是完全基于myba...

    gougoujiang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<