AJAX的出现与跨域处理

XMLHttpRequest JSON AJAX CORS 四个名词来开会

如何发请求

在前端的世界里也逛荡了很多日子了,目前已经get到大约5种发起请求的方式,主流的、非主流的。javascript

何种方式 请求方法
最多见的form表单 默认GET,多用POST,只此两种 会刷新页面或者新开页面
a 标签 GET请求 也会刷新页面或者新开页面
imgsrc属性 GET 只能以图片的形式展示
link标签 GET 只能以CSSfavicon的形式展示
script标签 GET 只能以脚本的形式运行

但是html

  • 咱们可能想用GET POST PUT DELETE 方法
  • 不想刷新整个页面,想用一种更易于理解的方式来响应

AJAX出现

浏览器和服务器交互模式 V1.0

AJAX未出现以前,浏览器想从服务器得到资源,注意是获取资源,会通过以下一个过程前端

  • 浏览器发起请求->服务器接到请求响应给你HTML文档->浏览器收到资源,刷新页面,加载得到的的HTML。简略的过程

我称这种交互方式是 V1.0,此时仍是以获取资源为导向。后来随着时代的发展,人们日益增加的文化需求成为了社会的主要矛盾……有一天,小明看了一篇报道,他只是想在下面评论一下,发表对实事的亲切问候,问候完了,唉,你给我刷新页面干啥,我只是想评论一下啊。java

什么鬼

大概那是网民们第一次对 良好的用户体验 提出了要求。后来的苹果爸爸,把你们惯坏了,每天嚷着 "你这产品用户体验太差了"……node

彼时,微软仍是对web作出了很大的贡献的。git

交互模式2.0

大约1999年,微软发布IE 5.0版本,它容许JavaScript脚本向服务器发起HTTP请求。不过很遗憾,当时也没有火起来,直到2004年Gmail发布和2005年Google Map发布,才引发普遍重视。2005年,一个叫Jesse James Garrett的人提出了一个新术语----AJAX,它是一系列技术的组合体,全称是 Asynchronous JavaScript + XML(异步的JS和XML)能够阻止页面总体刷新,只是动态响应用户的操做,快速显示到局部,用户就能够很愉快的继续上网了。程序员

AJAXes6

能够看出IE当时仍是很猛的,随着IE 6.0 市场份额进一步扩大,IE已经把火狐整的半死不活,放眼整个浏览器市场,微软是当之无愧的王者,后来微软就把浏览器团队解散了……不得不说这是一波神操做,能与之媲美的操做大概只有残血我能反杀 塔下我能秀他了。微软强行为后续各家浏览器的发展提供了优秀的工程师,尤为是0八、09年出生的谷歌浏览器,再看现在的IE……github

既然AJAX是一系列的技术的组合体,接下来认识一下其中的几位主角web

XMLHttpRequest

XMLHttpRequest对象是用来在浏览器和服务器之间传输数据的。

古代的操做的是:

  1. 浏览器构造XMLHttpRequest实例化对象
  2. 用这个对象发起请求
  3. 服务器响应一个XML格式的字符串,是字符串,是字符串,是字符串,也就是说响应的第四部分是字符串。
  4. JS解析符合XML格式的字符串,更新局部页面。

什么是XML可扩展标记语言。

以上是最初的用法,用的是XML,前端代码片断以下

let request = new XMLHttpRequest() //实例化XMLHttpRequest对象
  request.onreadystatechange = () => {
    if (request.readyState === 4) {
      console.log('请求和响应都完毕了')

      if (request.status >= 200 && request.status <= 300) {
        console.log('说明请求成功了')
        console.log(request.responseText)
        let parser = new DOMParser()
        let xmlDoc = parser.parseFromString(request.responseText, "text/xml") 
        //用parser解析request.responseText
        let c = xmlDoc.getElementsByTagName('body')[0].textContent
        console.log(c)
      } else if (request.status >= 400)  {
        console.log('说明请求失败了')
        
      }
    }
    
  }
  request.open('GET', '/xxx') //配置request
  request.send()

服务器端的对应代码片断以下

...
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/xml;charset=utf-8')
    response.write(`
    
    <note>
      <to>木木</to>
      <from>少少</from>
      <heading>你好哇</heading>
      <body>很久不见啊</body>
    </note>
    `)
    response.end()
    ...

本地模拟的话,必定要记得开俩不一样的端口
例如:

node server.js 8001
node server.js 8002

XMLHttpRequest实例的详解

正如上面的前端代码片断写的同样,主要用到了open() send()方法, onreadystatechange readyState 属性。

  1. request.open(method, URL, async)方法。

    • 通常用三个参数,第一个参数是请求的方法,能够用GET POST DELETE PUT等等,URL是用访问的路径,async是是否使用同步,默认true,开启异步,不须要作修改便可,因此实际中只写前两个参数
    • 若是非要写false,开启同步,会对浏览器有阻塞效应,并且若是值为false,则send()方法不会返回任何东西,直到接受到了服务器的返回数据
  2. request.send()方法。

    • 发送请求. 若是该请求是异步模式(默认),该方法会马上返回. 相反,若是请求是同步模式,则直到请求的响应彻底接受之后,该方法才会返回
  3. readyState属性。

    • 描述请求的五个状态。

      • 0 === 常量 UNSENT (未打开) open()方法未调用
      • 1 === OPENED (未发送) 只是open()方法调用了
      • 2 === HEADERS_RECEIVED (已获取响应头) send()方法调用了,响应头和响应状态已经返回了
      • 3 === LOADING (正在下载响应体) 响应体下载中,responseText已经获取了部分数据
      • 4 === DONE (请求完成) 整个响应过程完毕了。 这个值是实际中用到的。
      • 只要不等于4,就表示请求还在进行中。
  4. responseText属性是这次响应的文本内容。
  5. onreadystatechange属性。

    • readyState属性的值发生改变,就会触发readyStateChange事件。
    • 咱们能够经过onReadyStateChange属性,指定这个事件的回调函数,对不一样状态进行不一样处理。尤为是当状态变为4的时候,表示通讯成功,这时回调函数就能够处理服务器传送回来的数据。即前面的代码片断的处理方式。
  6. 其余的方法、属性、事件详见阮一峰博客MDN文档

习惯用javaScript的前端是不想和XML打交道的,应该用一种符合js风格的数据格式语言。


JSON

后来一个美国程序员道格拉斯·克罗克福特发明了JSON,解决了上面的问题,这货还写了一本蝴蝶书JavaScript语言精粹,还发明了一个JS校验器 ----JSLint。

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于 JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用彻底独立于语言的文本格式,可是也使用了相似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

以上是JSON官网的简介,能够看出它是一门全新的语言,不是JavaScript的子集

  1. JSON很简单,数据类型和JS有点不一样的地方。
JavaScript JSON
string "string" 必须写双引号
number number
object {"object": "name"} 必须双引号
undefined 没有
null null
boolean 直接写true false
array array
function 没有
variable
  1. 浏览器的全局对象window上有JSON对象,直接使用window.JSON.parse(string)
let string = request.responseText
let json = window.JSON.parse(string) //string 要符合JSON的格式

以上是JSON解析部分的代码。

此时服务器端代码是

response.statusCode = 200
response.setHeader('Content-Type', 'text/json;charset=utf-8')
response.write(`
    {
      "note" : {
        "to" : "木木",
        "from" : "少少",
        "heading" : "你好哇",
        "content" : "很久不见啊"
      }
    }
`)
  1. 咱们浏览器有同源政策,不是同协议 同域名 同端口 的网页没法相互访问。

4.AJAX刚好是同源政策的拥趸

CORS

  1. 若是AJAX向非同源的地址发起请求,会报错。

    • 这种错误没法经过状态码识别,由于HTTP回应的状态码有多是200,也就是说即便你看到了200的正确码,也没有用
  2. 可是form表单无视同源政策,能够发起跨域请求。
<button id="myButton">点我</button>
<form action="https://www.baidu.com" method="get">
   <input type="password" name="password">
   <input type="submit" value="提交">
</form>

上述请求响应都没有问题
然而对于AJAX就不行

...
request.open('GET', 'http://www.baidu.com')
...

同源的保护

  • 这是为何呢,由于
原页面用 form 提交到另外一个域名以后,原页面的脚本没法获取新页面中的内容,因此浏览器认为这是安全的。
而 AJAX 是能够读取响应内容的,所以浏览器不能容许你这样作。若是你细心的话你会发现,其实请求已经发送出去了,你只是拿不到响应而已。
因此浏览器这个策略的本质是,一个域名的 JS ,在未经容许的状况下,不得读取另外一个域名的内容。但浏览器并不阻止你向另外一个域名发送请求。

做者:方应杭
连接:https://www.zhihu.com/questio...
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。


那么如何让AJAX跨域发起请求呢。
答案是CORS

  1. CORS目前是W3C的标准,它容许浏览器跨域发起XMLHttpRequest请求,并且能够发起多种请求,不像JSONP只能发起GET请求,全称是"跨域/源资源共享"(Cross-origin resource sharing)。

request.open('GET', 'http://wushao.com:8001/xxx') //配置request
  • 服务器端的代码须要作以下处理
response.setHeader('Access-Control-Allow-Origin', 'http://shaolin.com:8002')

必定要注意是谁去访问谁,8001去访问8002,那么8001的前端代码要告诉8002的后端代码,我们是一家人,你和浏览器说说别让它禁我了。

AJAX一些其余知识

既然能够发请求,那么请求头的四部分如何得到的,响应的四部分又是如何得到呢

得到请求和响应头
  1. 得到请求头的方法
request.open('GET', 'http://shaolin.com:8002/xxx')// 请求的第一部分
request.setRequestHeader('Content-Type', 'x-www-form-urlencoded')//请求的第二部分
request.setRequestHeader('wushao', '18') //请求的第二部分
request.send('我要设置请求的第四部分') //请求的第四部分
request.send('name=wushao&password=wushao') //请求的第四部分

对应的典型的http请求四部分

GET /xxx HTTP/1.1
HOST: http://shaolin.com:8002
Content-Type: x-www-form-urlencoded
wushao: 18

name=wushao&password=wushao
  1. 得到响应的方法
request.status //响应的第一部分 200
request.statusText //响应的第一部分 OK
request.getAllResponseHeaders //响应的第二部分,这个方法好啊,所有的响应头
request.getResponseHeader('Content-Type') //响应的第二部分具体的
request.responseText //响应的第四部分

对应的典型的http响应的四部分

HTTP/1.1 200 OK
Content-Type: text/json;charset=utf-8

{
      "note" : {
        "to" : "木木",
        "from" : "少少",
        "heading" : "你好哇",
        "content" : "很久不见啊"
      }
 }

回顾一下各个status对应的意思

100
200 === OK,请求成功
301 === 被请求的资源已永久移动到新位置
302 === 请求临时重定向,要求客户端执行临时重定向
304 === 和上次请求同样,未改变
403 === 服务器已经理解请求,可是拒绝访问
404 === 请求失败,服务器上没有这个资源
502 === 做为网关或者代理工做的服务器尝试执行请求时,从上游服务器接收到无效的响应。
503 === Service Unavailable 因为临时的服务器维护或者过载,服务器当前没法处理请求。
练习一下JQuery封装AJAX
  1. 初级的jq封装

这是一个很简陋的效果,首先我仍是把jq假设的很简单,就是一个window的属性,请轻喷……

window.jQuery = function (nodeOrSelector) {
  let nodes = {}
  nodes.addClass = function () {}
  nodes.html = function () {}
  return nodes
}

window.jQuery.ajax = function (options) {
  let url = options.url
  let method = options.method
  let headers = options.headers
  let body = options.body
  let successFn = options.successFn
  let failFn = options.failFn

  let request = new XMLHttpRequest() //实例化XMLHttpRequest对象
  request.open(method, url) 
  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) {
        successFn.call(undefined, request.responseText)
      } else if (request.status >= 400)  {
        failFn.call(undefined, request)
      }
    }
  }
  request.send(body)
}

以上就是jq对ajax的简陋的封装,ajax()方法接受一个对象做为参数,这个对象有不少键。这些键就是http请求的头的各个部分,以及一个成功函数和一个失败函数。

myButton.addEventListener('click', (e) => {
    
  window.jQuery.ajax ({
    url: '/xxx',
    method: 'POST',
    headers: {
      'content-type': 'application/x-www-form-urlencoded',
      'wushao': '18'
    },
    body: 'a=1&b=6', 
    successFn: (x) => {
     ...
    }, 
    failFn: (x) => {
     ...
    }
  }) 
})

以上就是简化后的使用方法,给button绑定事件的时候,函数体直接就是ajax()

  1. 目前你会发现options这个对象傻傻的,由于总有一些用户不但愿只传一个参数。因此咱们稍微改造一下。
let url
  if (arguments.length === 1) {
    url = options.url
  } else if (arguments.length === 2) {
     url = arguments[0]
     options = arguments[1]
  }

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

加了一点,判断ajax()的参数个数。

  1. 一千我的有一千零一个成功或失败函数的写法,因此为了维护世界和平,你们约定俗成了一套理论 Promise then( )
//Promise这个对象呢,大概长这个样子,真实面目我是没见过
//简单的写一下promise
window.Promise = function (fn) {
//...一些其余代码
return {
  then: function () {}
 }
}

Promise这个构造函数呢,又会返回一个函数,这个返回的函数一个then属性,value又是一个函数。到处都体现着函数是第一公民的地位!!!
那咱们能够利用这个强大的Promise对象搞一些事情了。

//第一步的代码改形成这样,第一步用到了ES6的解构赋值法
window.jQuery.ajax = function ({url, method, body, headers}) {
  return new Promise(function (resolve, reject) {
    let request = new XMLHttpRequest()
    request.open(method, url)

    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)
        } else if (request.status >= 400) {
          reject.call(undefined, request)
        }
      }
    }
    request.send(body)
  })
}

关于解构赋值:ES6 容许按照必定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)
详见ES6解构赋值

//通过上面这么一折腾,能够很简单的使用了
myButton.addEventListener('click', (e) => {
   let promise = window.jQuery.ajax({
     url: '/xxx',
     method: 'get',
     headers: {
      'content-type': 'application/x-www-form-urlencoded',
      'wushao': '18'
     }
   })
   
   promise.then(
     (responseText) => {
       console.log(responseText)
     },
     (request) => {
       console.log(request)
     }
   ) 
})

注意then能够传入两个函数,第一个函数表示成功了执行这个,第二个函数表示失败了执行这个,并且能够进行链式调用,一直点下去。

  1. 因此实际上jq的写法大可能是这么写的
myButton.addEventListener('click', (e) => {
    $.ajax({
      url: '/xxx',
      type: 'GET',
    }).then(
      (responseText) => {
        console.log(responseText)
        return responseText
      },
      (request) => {
        console.log('error')
        return '已经处理'
      }
    ).then(
      (responseText) => {
        console.log(responseText)
      },
      (request) => {
        console.log(error2)
      }
    )

})

链式调用的意思就是:成功函数成功了,就执行第二个then的第一个函数;成功函数失败了,就执行第二个then的第二个函数。

完整代码详见个人gitHub

相关文章
相关标签/搜索