JavaScript基础篇(四) AJAX

Ajax 没出现前,JavaScript 在大多数开发者眼里应该还是个玩具语言,毕竟在那个刀耕火种的年代,压根就没有前端开发师这个角色,不是个全栈你都不好意思称呼自己是个程序猿,所以在那个年代出现的大牛也比较多。AjaxNode 发布之后,随着时代的发展,人们对页面的 UI 设计和交互体验也越来看重,前端所需要的承担的职责也越来越多,导致前后端的界限也越来越明显,进而衍生出了前端工程师这个职位!当然前端生态现在也是欣欣向荣,例如服务端、可视化、3D动画、小程序、APP等都可以通过 JavaScript 来实现。说了这么多,其实还是想突出 Ajax 的重要性,毕竟在前端历史上有着承上启下的作用。

说起 Ajax ,感觉里程碑式的 Jquery 真的必须留下姓名,我在两年前写过一篇为什么github放弃jQuery,但是我们不得不承认 Jquey 在前端历史上的地位。历史的车轮纵然是滚滚向前,但是照亮行业的星辰还是值得每一个人肃然起敬。煽情完毕,进入主题:

Ajax 的作用不过多赘述了,主要是连接前后端通信和数据传输用的,像我们在 jQuery 中写的 $.ajaxvue 中使用的 axios,还有基于传统语法优化后的 fetch,其实都是 ajax 的一种封装应用。

原生 AJAX 请求的五个步骤:


  • 创建一个XMLHttpRequest异步对象
  • 设置请求方式和请求地址
  • 接着,用send发送请求
  • 监听状态变化
  • 最后,接收返回的数据

结合上面的五个步骤,我们先来手写一下代码:

const xhr = new XMLHttpRequest()
xhr.open('GET', './data/test.json', true)
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(JSON.parse(xhr.responseText))
    } else {
      console.log('其它情况...')
    }
  }
}
xhr.send()

代码结合上述步骤看,应该很清晰,我这里就罗列一下你可能会觉得奇怪的地方:

  • xhr.open('GET', './data/test.json', true) 第三个参数 true 代表啥?

第三个参数为 true,则表示 JavaScript 异步执行,不等待后台返回直接向下执行。而为 false 的时候,表示同步执行,等待返回后再执行下一步。

  • xhr.readyState === 4 是什么鬼,为啥不是等于其他值偏偏等于 4 呢?

0:(未初始化),还没有调用 send() 方法。
1:(载入) 已调用 send() 方法,正在发送请求。
2:(载入完成) send() 方法执行完成,已经接收到全部响应内容。
3:(交互) 正在解析响应内容。
4:(完成) 响应内容解析完成,可以在客户端调用。

  • xhr.status == 200 又代表啥呢,有没有其它可选值?

2xx:一般代表请求成功并正确返回后端传给我们的值,例如:200
3xx:需要重定向,浏览器直接跳转,例如:301、302、304
4xx:客户端请求错误,例如:404、403
5xx:服务端错误

跨域


了解跨域之前,我们先认识一下 同源策略 这个概念:

  • ajax 请求时,浏览器要求当前网页和 server 必须同源(保证安全)
  • 同源的定义:协议、域名、端口,三者必须完全一致

举个栗子:

前端:http://a.com:8080/
server: https://b.com/api/xxx

上述两个链接,协议不同(一个 http 协议,一个 https 协议),端口不同(一个 8080 端口,一个没写端口号继承默认的 80 端口),域名也不同(一个 a.com,另一个 b.com)。

但是加载图片或者 css/js 可无视同源策略。即我们做的网站可以直接使用淘宝的图片,或者直接引用淘宝线上的 css 样式。向我们使用 cdn 基本就是这个道理。

认识跨域:

1、如果我们的 ajax 请求不满足同源策略规定的任意一项,即可被认为存在跨域请求行为。
2、所有的跨域,都必须经 server 端允许和配合 。
3、未经 server 端允许就实现跨域,说明浏览器有漏洞,危险信号。

认识 JSONP

jsonp 应该算是以前处理跨域的一种方法,并且存在很多限制(比如说只能处理 get 请求的跨域)。其实现在我们用的 vue 这些框架来写的话跨域的配置基本都是使用代理形式,也比较简单易懂,这里就不展开。jsonp 的实现原理其实也很简单,就是利用我们前面说的 css/js 可无视同源策略,干巴巴的文字肯定不如代码来的痛快:

a.html => 'http://192.168.0.105:8000/ajax.html' // a.html的url
<script src="http://192.168.0.105:8080/c.js"></script> // a.html 里面写的内容
c.js => 'http://192.168.0.105:8080/c.js' // c.js 的url
alert(111) // c.js 里面写的内容

如果我们要在 a.html 里面用 ajax 请求 c.js 里面返回的数据,受制于同源策略两者之间访问肯定是存在跨域行为的(它们俩端口号不一样),所以导致它们俩无法直接通信。但是如果我们通过 server 端的帮助,将要返回的数据放入到一个新的脚本文件中,然后把脚本文件的地址返回给我们,我们在将这个脚本地址手动添加到 html 页面上,那么我们就能直接获取到这个文件里面的数据了。例如上述代码,a.html 可以直接读取 'c.js' 中的内容并正确弹出 111

我们先来看一段跨域的代码及报错提示:

// 请求代码就用我们最初手写的 (文件路径:http://192.168.0.105:8000/ajax.html)
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://192.168.0.105:8080/d.json', true)
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(JSON.parse(xhr.responseText))
    } else {
      console.log('其它情况...')
    }
  }
}
xhr.send()

// d.json(文件路径:http://192.168.0.105:8080/d.json)
{"name": "zhangsan"}

具体的报错提示一般如下:


跨域报错.png

其实就是两者端口号不同,所以导致跨域,接下来我们用我们刚刚说的 jsonp 的原理实现跨域正常请求(这里我们将后端模拟返回的数据放到 c.js 中):

// a.html
<script>
  function abc(data) {
    console.log(data.message) // zhangsan
  }
</script>
<script src="http://192.168.0.105:8080/c.js"></script>
// c.js
<script>
abc({
  message: 'zhangsan'
})
</script>

看懂了吗?后端处理要返回的 json 数据,然后放入到 abc 函数中,前端就可以通过 abc 函数直接拿到后端给我们返回的对象。这其实就是 JSONP 的简单实现模式,或者说是 JSONP 的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将 JSON 数据形式作为参数传递,完成回调。将 JSON 数据填充进回调函数。

当然,一般情况下来说 <script src="http://192.168.0.105:8080/c.js"></script> 这句话肯定是动态添加的,因为这个 src 的地址我们提前是不知道的,只有后端告诉我们了,我们在将它动态添加到页面上,所以一般向下面的代码这样写:

<script>
  function abc(data) {
    console.log(data.message); // zhangsan
  }
  //添加<script>标签的方法
  function addScriptTag(src) {
    var script = document.createElement('script');
    script.setAttribute("type", "text/javascript");
    script.src = src;
    document.body.appendChild(script);
  }
  window.onload = function () {
    addScriptTag("http://192.168.0.105:8080/c.js");
  }
</script>

上面的例子是最简单的JSONP的实现模型,不过它还算不上一个真正的JSONP服务。我们来看一下真正的JSONP服务是怎么样的,比如Google的ajax搜索接口:http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=?&callback=,看这个请求,你会发现后面有一个 ?q= 这个问号是表示你要搜索的内容,最重要的是第二个 callback=? 这个是正如其名表示回调函数的名称,也就是将你自己在客户端定义的回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调。

<script>
  window.onload = function () {
    // 这里的 q 可以为我们查询的关键字,而 callback 为我们前端约定给后端返回的回调函数方法名
    addScriptTag("http://192.168.0.105:8080/c.js?q=aaa&callback=abc");
  }
</script>

当然,我们也可以直接使用 jquery ,因为它里面已经帮我们做了 jsonp 请求的代码封装,在这里我们在将上面的代码用 jquery 在重新实现一遍:

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
  $.ajax({
    url: "http://192.168.0.105:8080/c.js",
    type: "GET",
    dataType: "jsonp", //指定服务器返回的数据类型
    jsonpCallback: 'abc', // 这里服务端返回的回调函数方法名
    success: function (data) {
      console.log(data) // {message: "zhangsan"}
    }
  });
</script>

其实看上面的代码我们就知道,jsonp 限制不少,根本不好用,前端用的不舒服,后端更是觉得麻烦,所以我们大致了解实现的过程就行了,因为现在基本不会采用 jsonp 的方式来解决跨域。像现在比较流行的前端框架都有自己处理跨域的成熟方法,也可以通过服务器端配置来做跨域设置,说实话无论哪一种真的都完爆 jsonp

了解 cors,服务器设置 http header 实现跨域请求

// 第二个参数填写允许跨域的域名称,* 表示所有域名都可以,不建议直接用 *
response.setHeader('Access-Control-Allow-Origin', 'http://192.168.0.105:8080 || *')
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With')
response.setHeader('Access-Control-Allow-Methods', 'PUT,GET,POST,DELETE')
// 接收跨域的 cookie
response.setHeader('Access-Control-Allow-Credentials', 'true')

这种主要还是后端进行配置,一次配置再无跨域烦恼,我们毕竟是前端工程师,简单了解就好了。

结语


其实应该用 promise 手写一个简易的 ajax 来做总结的,但是 promiseasync/await 我都写过类似的文章了,这里就不在赘述了!因为像现在非常流行的 axios 基本也都是 promise 封装 XMLHttpRequest 对象进而实现的!!!对 Promise 感兴趣但是了解又不深入的可以直接看我写的 Promise基础详解。如果文中有不对的地方或者理解有误的地方欢迎大家提出并指正。每一天都要相对前一天进步一点,加油!!!

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