手写 AJAX

目录

  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

JSON(JavaScript Object Notation,JavaScript对象表示法)是一种由 Douglas Crockford 构想和设计、轻量级的数据交换语言。它是 JavaScript 的一个子集,因此 JSON 在语法上保留了很多 JavaScript 的特征。

区别:

  1. JSON 没有 function、undefined,也没有 Number 中的 NaN 和 Infinity
  2. JSON 字符串的首尾必须是双引号,这意味着对象的键也必须加上双引号
  3. JSON 只是一种数据格式,数据格式其实就是一种规范,格式、形式、规范是不能用来存诸数据的。因此诸如 var obj={"width":100,"height":200,"name":"rose"} 这样的不能称之为 JSON 对象,而是一种 JSON 格式的 JS 对象。

如下是 JavaScript 与 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 有更深入的理解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容