自己实现AJAX

如何发请求?

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

前端需要一种方式实现:

  1. get、post、put、delete等请求都行
  2. 想以什么方式展示就以什么方式展示
AJAX

AJAX(async javascript and XML) 异步的javascript和XML
满足如下技术叫AJAX:

  1. 使用XMLHttpRequest发请求
  2. 服务器返回XML格式的字符串(现已升级为返回JSON格式字符串)
  3. JS解析XML,并更新局部页面
原生JS发送AJAX请求
let request = new XMLHttpRequest()  //产生request
request.open('get','http://jack.com/xxx')  //配置request
request.send()  //发送request
request.onreadystatechange = () => {  //监听request状态的变化
    if(request.readyState === 4){  //请求响应都完毕了
        if(request.status >= 200 && request.status < 300){  //请求成功 3xx也有可能为成功,这里不考虑
            let string = request.responseText
            let object = window.JSON.parse(string)  //把符合JSON语法的字符串转换成JS对应的值
        }else if(request.status >= 400){ //请求失败
        }
    }
}
readyState

请求的5种状态

状态 描述
0 UNSENT(未打开) open()方法还没有调用
1 OPENED(未发送) open()方法已经被调用
2 HEADERS.RECEIVED(已获取响应头) send()方法已经被调用
3 LOADING(正在下载响应体) 响应体下载中;responseText中已经获取了部分数据
4 DONE(请求完成) 整个请求过程已经完毕
setInterval( () => {
    console.log(request.readyState)
},1)  //试试每毫秒打印一次readyState

上面代码每毫秒打印一次readyState,会发现一般无法打印出所有的状态,因为请求的过程太快了。

onreadystatechange

使用onreadystatechange可以监听readyState的每次变化

request.onreadystatechange = () => {
    console.log(request.readyState)
}
status

该请求的响应状态码,只读。

if(request.status >= 200 && request.status < 300){ //3xx也可能成功,此处不考虑
    console.log('请求成功')
}else if(request.status >= 400){
    console.log('请求失败')
}
responseText

此次请求的响应文本。
当请求未成功或还未发送时为null。只读。

JS vs JSON

JSON和JS是两门不同的语言,道格拉斯(JSON作者)抄袭了JS,所以JSON很像JS。JSON官网

  • JSON没有function和undefined
  • JSON的字符串首尾必须是双引号
  • JSON不能表示复杂对象,只能表示哈希
  • JSON没有变量
  • JSON没有原型链

响应第四部分是字符串,可以是符合JSON语法的字符串。

//把符合JSON语法的字符串转换成JS对应的值
let string = request.responseText
let object = window.JSON.parse(string)

至此AJAX得到升级,不再返回XML格式的字符串,而是返回更亲近JS的JSON格式的字符串。

同源策略

只有 协议+端口+域名 一模一样才允许发AJAX请求
因为AJAX可以读取响应内容,所以浏览器不允许一个域名的JS,在未经允许的情况下读取另一个域名的内容,但是浏览器并不阻止你向另一个域名发请求。
CORS可以告诉浏览器,我俩是一家的,别阻止他。
在jack.com的服务器写上

response.setHeader('Access-Control-Allow-Origin','http://frank.com')

这样frank.com就可以访问jack.com了。
CORS (Cross-Origin Resource Sharing) 跨来源资源共享。
突破同源策略 === 跨域

AJAX的所有功能

AJAX:用JS发送请求,用JS处理响应。

  • 客户端JS发起请求(浏览器上的)
    1. 第一部分 request.open('get','./xxx') 动词,路径
    2. 第二部分 request.setRequestHeader('Content-Type','x-www-form-urlencoded')
    3. 第四部分 request.send('a=1&b=2')
  • 服务端的JS发送响应(Node.js上的)
    1. 第一部分 request.status/request.statusText 例如200/OK
    2. 第二部分 request.getResponseHeader()/request.getAllResponseHeaders()
    3. 第四部分 request.responseText

通过AJAX可以任意设置请求四部分中的所有东西(有些不安全的不让设置),也可以获取响应四部分中的所有内容。

XMLHttpRequest.setRequestHeader()是设置HTTP请求头的方法,此方法必须在open()方法和send()方法之间调用。

自己封装jQuery.ajax
window.jQuery.ajax = function(url, method, body, successFn, failFn){
    //... 
}
//给参数命名,有结构的参数--对象(JS里只有对象有结构)

window.jQuery.ajax = function(options){
    let url = options.url
    let method = options.method
    let body = options.body
    let successFn = options.successFn
    let failFn = options.failFn
    
    let request = new XMLHttpRequest()
    request.open(method, url) 
    /*for(let key in headers) {
      let value = headers[key]
      request.setRequestHeader(key, value)
    }*/ //设置headers,下文
    request.onreadystatechange = ()=>{
      if(request.readyState === 4){
        if(request.status >= 200 && request.status < 300){
          successFn.call(undefined, request.responseText)
        }else if(request.status >= 400){
          failFn.call(undefined, request)
        }
      }
    }
    request.send(body)
  }
//调用
let obj = {
    url: '/xxx',
    method: 'get',
    successFn: () => {}
    failFn: () => {}
}
window.jQuery.ajax(obj)
//调用可以直接写成
window.jQuery.ajax({
    url: '/xxx',
    method: 'get',
    successFn: () => {}
    failFn: () => {}
})
//如果请求成功时需要执行两个函数
successFn: (x) => {
    f1.call(undefined,x)
    f2.call(undefined,x)
}

如果需要设置headers

headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'jack': '18'
}

//在open()和send()之间遍历headers,设置请求头
let headers = options.headers

for(let key in headers) {
      let value = headers[key]
      request.setRequestHeader(key, value)
}

jQuery.ajax有两种传参方式,传一个参数,或传两个参数,第一个为url

window.jQuery.ajax = function(options){
    let url
    if(arguments.length === 1){ //传一个参数
        url = options.url
    }else if(argument.length === 2){ //传两个参数
        url = arguments[0]  //url是第一个参数
        options = arguments[1]
    }
    let method = options.method
    let body = options.body
    let successFn = options.successFn
    let failFn = options.failFn
    let headers = options.headers
    //...
}
ES6语法之解构赋值

解构赋值语法是一个JavaScript表达式,这是的可以将 值从数组属性从对象 提取到不同的变量。

    let url = options.url
    let method = options.method
    let body = options.body
    let successFn = options.successFn
    let failFn = options.failFn
    let headers = options.headers

//上面代码可以写成
let{url,mmethod,body,successFn,failFn,headers} = options

//直接从第一个参数里解构,拿到几个变量,同时用let声明几个变量
window.jQuery.ajax = function({url,method,body,successFn,failFn,headers}){}
Promise

不同的库风格不一样,如果不看文档,不知道成功时传什么参数,失败时传什么参数。
例如我自己封装的jQuery.ajax中,成功时传successFn,失败时传failFn,与原版的jQuery是不同的。

$.ajax({
    url:'/xxx',
method: 'get',
}).then(
    (responseText) => {console.log(responseText)}, //成功时执行第一个参数
    (request) => {console.log'error'} //失败时执行第二个参数
)

上面代码中,使用.then(),它有两个函数作为参数,成功时执行第一个参数,失败时执行第二个参数,不用再给成功、失败时执行的函数命名。

.then()后面可以继续续接.then(),可以基于上一次的处理结果继续处理。

把自己封装的jQuery.ajax变成promise形式
window.jQuery.ajax = function({url, method, body, headers}){
    return new Promise(function(resolve, reject){
      let request = new XMLHttpRequest()
      request.open(method, url) // 配置request
      for(let key in headers) {
        let value = headers[key]
        request.setRequestHeader(key, value)
      }
      request.onreadystatechange = ()=>{
        if(request.readyState === 4){
          if(request.status >= 200 && request.status < 300){
            resolve.call(undefined, request.responseText) //成功调用resolve
          }else if(request.status >= 400){
            reject.call(undefined, request)  //失败调用reject
          }
        }
      }
      request.send(body)
    })
  } //返回值是一个Promise实例对象,这个实例对象有then属性;then也会返回一个Promise对象,所以可以再接.then()
  

全部代码

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

推荐阅读更多精彩内容