手写 AJAX

作者: 许骁Charles | 来源:发表于2019-05-25 16:40 被阅读2次

    目录

    1. 没有 AJAX 的年代,怎么发请求
    2. AJAX 是什么
    3. XMLHttpRequest 的实例属性
    4. XMLHttpRequest 的实例方法
    5. 手写 AJAX
      • (1)原生 ajax
      • (2)封装 jQuery.ajax
    6. 总结

    没有 AJAX 的年代,怎么发请求

    1. form 可以发请求,但是会刷新页面或新开页面
    1. a 可以发 get 请求,但是也会刷新页面或新开页面
    1. img 可以发 get 请求,但是只能以图片的形式展示
    1. link 可以发 get 请求,但是只能以 CSS、favicon 的形式展示
    1. script 可以发 get 请求,但是只能以脚本的形式运行

    AJAX 是什么

    AJAX(Asynchronous JavaScript and XML),指的是通过 JavaScript 的异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。

    后来,AJAX 这个词就成为 JavaScript 脚本发起 HTTP 通信的代名词,也就是说,只要用脚本发起通信,就可以叫做 AJAX 通信。

    AJAX 的步骤

    1. 创建 XMLHttpRequest 实例
    2. 发出 HTTP 请求
    3. 服务器返回 XML 格式的字符串
    4. JS 解析 XML,并更新局部页面

    不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON

    XMLHttpRequest 对象是 AJAX 的主要接口,用于浏览器与服务器之间的通信。尽管名字里面有 XML 和 HTTP,它实际上可以使用多种协议(比如 file 或 ftp),发送任何格式的数据(包括字符串和二进制)。

    注意:AJAX 只能向同源网址(协议、域名、端口都相同)发出 HTTP 请求,如果发出跨域请求,就会报错。

    XMLHttpRequest 的实例属性

    XMLHttpRequest.readyState

    XMLHttpRequest.readyState 属性返回一个 XMLHttpRequest 代理当前所处的状态。

    状态 描述
    0 UNSENT 代理被创建,但尚未调用 open() 方法
    1 OPENED open() 方法已经被调用
    2 HEADERS_RECEIVED send() 方法已经被调用,并且头部和状态已经可获得
    3 LOADING 下载中; responseText 属性已经包含部分数据
    4 DONE 下载操作已完成

    XMLHttpRequest.status

    XMLHttpRequest.status 属性返回一个整数,表示服务器回应的 HTTP 状态码。

    XMLHttpRequest.onreadystatechange

    XMLHttpRequest.onreadystatechange 属性指向一个监听函数。readystatechange 事件发生时(实例的 readyState 属性变化),就会执行这个属性。

    XMLHttpRequest.responseText

    XMLHttpRequest.responseText 属性返回从服务器接收到的字符串,该属性为只读。只有 HTTP 请求完成接收以后,该属性才会包含完整的数据。

    XMLHttpRequest 的实例方法

    XMLHttpRequest.open()

    XMLHttpRequest.open() 方法用于指定 HTTP 请求的参数,或者说初始化 XMLHttpRequest 实例对象。

    它一共可以接受五个参数:

    • method:表示 HTTP 动词方法,比如GET、POST、PUT、DELETE、HEAD等
    • url: 表示请求发送目标 URL
    • async(可选): 布尔值,表示请求是否为异步,默认为 true。如果设为 false,则 send() 方法只有等到收到服务器返回了结果,才会进行下一步操作。由于同步 AJAX 请求会造成浏览器失去响应,许多浏览器已经禁止在主线程使用,只允许 Worker 里面使用。所以,这个参数轻易不应该设为false。
    • user(可选):表示用于认证的用户名,默认为空字符串
    • password(可选):表示用于认证的密码,默认为空字符串

    注意:再次使用 open(),等同于调用 abort()

    XMLHttpRequest.send()

    XMLHttpRequest.send()方法用于实际发出 HTTP 请求。

    它的参数是可选的:

    • 如果不带参数,就表示 HTTP 请求只有一个 URL,没有数据体,典型例子就是 GET 请求
    • 如果带有参数,就表示除了头信息,还带有包含具体数据的信息体,典型例子就是 POST 请求

    GET 请求:

    var xhr = new XMLHttpRequest();
    xhr.open('GET',
      'http://www.example.com/?id=' + encodeURIComponent(id),
      true
    );
    xhr.send(null);
    

    POST 请求:

    var xhr = new XMLHttpRequest();
    var data = 'email='
      + encodeURIComponent(email)
      + '&password='
      + encodeURIComponent(password);
    
    xhr.open('POST', 'http://www.example.com', true);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.send(data);
    

    XMLHttpRequest.setRequestHeader()

    XMLHttpRequest.setRequestHeader()方法用于设置浏览器发送的 HTTP 请求的头信息。

    该方法必须在open()之后、send()之前调用。
    该方法接受两个参数。第一个参数是字符串,表示头信息的字段名,第二个参数是字段值。

    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
    xhr.send(JSON.stringify(data));
    

    XMLHttpRequest.getResponseHeader()

    XMLHttpRequest.getResponseHeader()方法返回 HTTP 头信息指定字段的值。

    XMLHttpRequest.getAllResponseHeaders()

    XMLHttpRequest.getAllResponseHeaders()方法返回一个字符串,表示服务器发来的所有 HTTP 头信息。

    格式为字符串,每个头信息之间使用 CRLF 分隔(回车+换行),如果没有收到服务器回应,该属性为 null。如果发生网络错误,该属性为空字符串。

    手写 AJAX

    简而言之,AJAX 就是在浏览器通过 XMLHttpRequest 对象, 构造(set)HTTP 请求和获取(get)HTTP 响应的技术。

    那么 AJAX 具体如何实现?

    1. JS 设置(set)任意请求 header
      • 请求内容第一部分 request.open('get', '/xxx')
      • 请求内容第二部分 request.setRequestHeader('content-type','x-www-form-urlencoded')
      • 请求内容第四部分 request.send('a=1&b=2')
    2. JS 获取(get)任意响应 header
      • 响应内容第一部分 request.status / request.statusText
      • 响应内容第二部分 request.getResponseHeader() / request.getAllResponseHeaders()
      • 响应内容第四部分 request.responseText

    (1)原生 ajax

    了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。

    version 1.0:

    myButton.addEventListener('click', function(){
        ajax()
    })
    
    function ajax() {
        let request = new XMLHttpRequest()
        request.open('get', 'https://www.google.com')
        request.onreadystatechange = () => {
            if (request.readyState === 4) {
                if (request.status >= 200 && request.status <300) {
                    let string = request.responseText
                    let object = JSON.parse(string)
                }
            }
        }
        request.send()
    }
    

    在 1.0 版本中,并没有设置请求报头,因为是 GET 请求,所以 send() 中也没有设置请求主体。并且,其内容是写死的,我们应该从变量来获取更好。

    (2)封装 jQuery.ajax

    需求:实现一个满足这些 API 的 jQuery.ajax(url, method, body, success, fail)。

    在之前的文章中,我实现了一个简易版 jQuery,这里就在它的基础上再添加函数 ajax。

    version 2.0:

    myButton.addEventListener("click", (e) => {
        $.ajax(
              '/xxx', 
              'post', 
              'a=1&b=2', 
              (responseText) => { console.log('success') }, 
              (request) => { console.log('fail') }
        )
    })
    
    window.jQuery = function(nodeOrSelector) {
        let nodes = {}
        return nodes
    }
    
    window.jQuery.ajax = function(url, method, body, success, fail) {
        let request = new XMLHttpRequest()
        request.open(method, url)
        request.onreadystatechange = () => {
            if (request.readyState === 4) {
                if (request.status >= 200 && request.status <300) {
                    success.call(undefined, request.responseText)
                } else if (request.status >= 400) {
                    fail.call(undefined, request)
                }
            }
        }
        request.send(body)      
    }
    
    window.$ = window.jQuery
    

    version 2.0 版本实现了 jQuery 版本的以变量传入的方式实现API的 ajax,但是封装得并不好,容易忘记每个参数是什么,并且诸如 GET 请求并不需要参数 body,因此这个位置应该填入 undefined 或者 null,这样代码就很丑。
    因此,我们需要传入一个有结构的参数来包含上述功能。

    version 3.0:

    myButton.addEventListener("click", (e) => {
        $.ajax({
            url: '/xxx',
            method: 'post',
            body: 'a=1&b=2',
            success: (responseText) => {
                console.log('success')
                console.log(responseText)
            },
            fail: (request) => {
                console.log('fail')
                console.log(request.status)
            }
        })
    })
    
    window.jQuery = function (nodeOrSelector) {
        let nodes = {}
        return nodes
    }
    
    window.jQuery.ajax = function (options) {
        let url = options.url
        let method = options.method
        let body = options.body
        let success = options.success
        let fail = options.fail
    
        let request = new XMLHttpRequest()
        request.open(method, url)
        request.onreadystatechange = () => {
            if (request.readyState === 4) {
                if (request.status >= 200 && request.status < 300) {
                    success.call(undefined, request.responseText)
                } else if (request.status >= 400) {
                    fail.call(undefined, request)
                }
            }
        }
        request.send(body)
    }
    
    window.$ = window.jQuery
    

    继续优化上面的代码,带入ES6中数组的解构赋值。

    //  ES 5
        let url = options.url
        let method = options.method
        let body = options.body
        let success = options.success
        let fail = options.fail
    
    //  ES 6
        let {url, method, body, success, fail} = options
    

    并且,由于 options 参数只使用了一次,那么可以直接省略掉。得到:

    version 4.0:

    myButton.addEventListener("click", (e) => {
        $.ajax({
            url: '/xxx',
            method: 'post',
            body: 'a=1&b=2',
            success: (responseText) => {
                console.log('success')
                console.log(responseText)
            },
            fail: (request) => {
                console.log('fail')
                console.log(request.status)
            }
        })
    })
    
    window.jQuery = function (nodeOrSelector) {
        let nodes = {}
        return nodes
    }
    
    window.jQuery.ajax = function ({ url, method, body, success, fail }) {
    
        let request = new XMLHttpRequest()
        request.open(method, url)
        request.onreadystatechange = () => {
            if (request.readyState === 4) {
                if (request.status >= 200 && request.status < 300) {
                    success.call(undefined, request.responseText)
                } else if (request.status >= 400) {
                    fail.call(undefined, request)
                }
            }
        }
        request.send(body)
    }
    
    window.$ = window.jQuery
    

    总结

    AJAX 非常重要,基本上,有了 AJAX 之后,前端才被称之为前端,在这之前的程序员,基本可以被称为页面仔。因此,深入理解 AJAX 的手动实现,如何设置和获取 request 和 response,完成 HTTP 请求,是学习的重点,也是面试常考的内容。

    这里只提到了比较浅显的 HTTP 相关知识,在后面补看《HTTP权威指南》后,会对 AJAX 有更深入的理解。

    相关文章

      网友评论

        本文标题:手写 AJAX

        本文链接:https://www.haomeiwen.com/subject/qqqeaqtx.html