JSONP和AJAX没有半毛钱关系

Jsonp 和 Ajax 有个毛关系啊!!!


这一点非常重要啊

大概是jQuery把Jsonp放到了ajax API里面, 所以很多人总认为JSONP是AJAX的一种

但是, 一定要记住
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系
JSONP和AJAX没有半毛钱关系

这样应该就能记住了吧。。。。


JSONP


JSONP利用的是script发送请求的原理
说白了就是我们利用script标签可以通过向src中填的地址发送get请求这个特点, 创建一个动态的script标签, 然后用这个标签向服务器发送一个请求的一种方法

假设
请求方: frank.com的前端程序员(浏览器)
响应方: jack.com的后端程序员(服务器)

  1. 请求方创建script, src指向响应方, 同时传一个查询参数 ?callback=xxx
  2. 响应方根据查询参数callback, 构造形如
    xxx.call(undefined, '数据')
    这样的响应
  3. 浏览器接收到响应, 就会执行xxx.call(undefined, ‘数据’)
  4. 那么请求方就知道了他要的数据

这就是 JSONP

约定:

  1. callbackName -> callback
  2. xxx -> 随机数,

完整的JSONP请求如下

let script = document.createElement('script')
let functionName = 'ss' + parseInt(Math.random()*100000)


//接收到响应后我们要执行的代码, result就是我们接收到的响应数据
window[functionName] = function(result) {
  ...
}


//发送请求的地址和参数
script.src = 'http://jack.com:8002/pay?callback=' + functionName


//script便签只有在页面中才会执行, 所以我们把他添加到body下
document.body.appendChild(script)


//当成功得到响应并执完代码后, 销毁动态script标签, 表示此次http通讯完成
script.onload = function(e){
  e.currentTarget.remove()
}

//当响应失败时, 同样销毁动态script标签, 表示此次http通讯完成
script.onerror = funciont(e){
  e.currentTarget.remove()
}



用jQuery则更简单

$.ajax({
  url: 'http://jack.com:8002/pay',
  dataType: 'Jsonp',
  success: function(response) {
    ...
  }
})


Jsonp为什么不支持POST请求?

  1. Jsonp通过动态创建script来实现的

  2. script只能发送get请求, 没办法发送post请求


AJAX


AJAX (async JavaScript and XML)异步的 JavaScript 和 XML

首先我们来总结一下, 我们在html中可以通过哪些方法发送请求

  • 用 form 可以发送请求, 但是会刷新或者新开页面
  • 用 a 可以发 get 请求, 但也会刷新页面或新开页面
  • 用 img 可以发送 get 请求, 但是只能以图片的形式展开
  • 用 link 可以发送 get 请求, 但是只能以 CSS 和 favicon 的形式展示
  • 用 script 可以发送 get 请求, 但是只能以脚本的形式运行, 这个就是JSONP的原理

那么, 有什么方法可以实现以下需求呢

  • 发送 get、post、put、delete 任意一种请求
  • 想用什么形式展示就用什么形式展示


微软牛逼了

IE5 的时候, 微软在JS中引入了ActiveX对象, 可以让JS直接发起HTTP请求, 这个技术在当时可算是一个牛逼炸了的功能, 这让我们可以很方便的在浏览器发送HTTP请求
随后Mozilla、safari、Opera等当时浏览器界的大佬们也抄袭了微软, 弄了一个XMLHttpRequest对象, 并被纳入W3C标准


AJAX产生了

Jesse James Garrett 将如下技术取名 AJAX: 异步的 JavaScript 和 XML

  1. 使用 XMLHttpRequest 发请求
  2. 服务器返回 XML 格式的字符串(现在主要使用 JSON 格式)
  3. JS解析 XML 格式字符串(现在基本上都是解析 JSON 格式字符窜), 并更新局部页面

这里有一点需要强调, 服务器返回的响应体是字符串, 并不是什么对象或者其他乱七八糟的东西, 就是字符串

如何使用XMLHttpRequest

最简单的AJAX请求

let xhr = new XMLHttpRequest() //创建一个AJAX请求对象
xhr.open('GET', '/xxx') //初始化HTTP请求, 包括但不限于用什么方法, url
xhr.send() //发送HTTP请求

xhr.onreadystatechagne() 这个方法是监听readystate改变的方法, 很好用


readyState的五种状态

对于readyState的五种状态的描述或者说定义,很多Ajax书(英文原版)中大都语焉不详

比较理想的解释方法应该以“状态:任务(目标)+过程+表现(或特征)”的表达模式来对这几个状态进行定义

readyState状态 状态说明
(0)未初始化 此阶段确认XMLHttpRequest对象是否创建,并为调用open()方法进行未初始化作好准备。值为0表示对象已经存在,否则浏览器会报错--对象不存在。
(1)载入 此阶段对XMLHttpRequest对象进行初始化,即调用open()方法,根据参数(method,url,true)完成对象状态的设置。并调用send()方法开始向服务端发送请求。值为1表示正在向服务端发送请求。
(2)载入完成 此阶段接收服务器端的响应数据。但获得的还只是服务端响应的原始数据,并不能直接在客户端使用。值为2表示已经接收完全部响应数据。并为下一阶段对数据解析作好准备。
(3)交互 此阶段解析接收到的服务器端响应数据。即根据服务器端响应头部返回的MIME类型把数据转换成能通过responseBody、responseText或responseXML属性存取的格式,为在客户端调用作好准备。状态3表示正在解析数据。
(4)完成 此阶段确认全部数据都已经解析为客户端可用的格式,解析已经完成。值为4表示数据解析完毕,可以通过XMLHttpRequest对象的相应属性取得数据。

由于 readystate 有这么多状态, 所以我们可以通过监听 readystate 的状态的改变, 获取改变后的readystate值, 并利用这个值判断请求是否发送成功

所以我们完善一下上面的AJAX请求

let xhr = new XMLHttpRequest()

//监听readystate改变
xhr.onreadystatechange = () => {

  //readyState变为4说明响应已完毕
  if(xhr.readyState === 4) {

    //http状态码为2xx说明接收响应成功
    if(xhr.status >= 200 && xhr.status < 300) {
      console.log(xhr.responseText)

    //http状态码大于等于400说明接收响应失败
    } else if(xhr.status >= 400) {
      console.log('error')
    }
  }
  xhr.open('GET', '/xxx') //初始化HTTP请求, 包括但不限于用什么方法, url
  xhr.send() //发送HTTP请求
}

通常response返回的都是一个符合JSON格式的字符串


JSON

JSON(JavaScript Object Natation)是一种轻量级的数据交换语言, 是JavaScript的子集, 这里要注意, JSON不是编程语言, 所以没有变量, 引用, 判断, 循环等语法

JSON 没有 functon和 undefined

JSON的字符串必须是"

JSON表示对象, key必须以双引号括起来

{"name": "Adam"}

JSON的格式举例

“abv” //字符串

123 //数字

null //null

{
  “name”: "Adam"
} //对象

["a", "b", "c"] //数组

有兴趣研究JSON的请点这里, 反正JSON所有的内容也就5分钟就学完了



还是上面的请求

let xhr = new XMLHttpRequest()

//监听readystate改变
xhr.onreadystatechange = () => {

  //readystate变为4说明响应已完毕
  if(xhr.readyState === 4) {

    //http状态码为2xx说明接收响应成功
    if(xhr.status >= 200 && xhr.status < 300) {
      console.log(xhr.responseText)

    //http状态码大于等于400说明接收响应失败
    } else if(xhr.status >= 400) {
      console.log('error')
    }
  }
  xhr.open('GET', '/xxx') //初始化HTTP请求, 包括但不限于用什么方法, url
  xhr.send() //发送HTTP请求
}



假设我们发送请求后, 服务器给我们返回的是 JSON 格式的字符串

{
  "note": {
    "to": "Eve",
    "from: "Adam",
    "heading": "Hello",
    "content": "hi"
  }
}

上面的是字符串啊, 记住是字符串啊!!!!!!!

所以我们需要用JSON.parse()将符合 JSON 格式的字符串转换成JavaScript对应的值, 上面的响应内容将会转化成对象.

这里又有个关于JSON的误区, 很多人认为JSON.parse()转换回来的都是对象, 这个其实是错的, 只不过大多数后端传回来的 JSON 格式字符串都是符合对象的写法, 所以将字符串转换后才是对象, 如果响应回来的字符串符合数组格式, 那么转换回来的就是数组

let Object = JSON.parse(xhr.responseText)



所以完整的AJAX请求如下

let xhr = new XMLHttpRequest()

//监听readystate改变
xhr.onreadystatechange = () => {

  //readyState变为4说明响应已完毕
  if(xhr.readyState === 4) {

    //http状态码为2xx说明接收响应成功
    if(xhr.status >= 200 && xhr.status < 300) {
      let Object = JSON.parse(xhr.responseText)
      console.log(Object)

    //http状态码大于等于400说明接收响应失败
    } else if(xhr.status >= 400) {
      console.log('error')
    }
  }
  xhr.open('GET', '/xxx') //初始化HTTP请求, 包括但不限于用什么方法, url
  xhr.send() //发送HTTP请求
}


XMLHttpRequest设置请求头

上面的例子是最典型的AJAX请求, 但是有些场景, 我们需要对请求头的内容进行设置

XMLHttpRequest.setRequestHeader() 是设置HTTP请求头部的方法。此方法必须在 open() 方法和 send() 之间调用。如果多次对同一个请求头赋值,只会生成一个合并了多个值的请求头。

语法

myReq.setRequestHeader(header, value)

参数
header
属性的名称。
value
属性的值。

XMLHttpRequest设置请求体

XMLHttpRequest.send() 方法用于发送 HTTP 请求。如果是异步请求(默认为异步请求),则此方法会在请求发送后立即返回;如果是同步请求,则此方法直到响应到达后才会返回。XMLHttpRequest.send() 方法接受一个可选的参数,其作为请求主体;如果请求方法是 GET 或者 HEAD,则应将请求主体设置为 null。

语法

void send();
void send(ArrayBuffer data);
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(DOMString? data);
void send(FormData data);

如果发送的数据是Document对象,需要在发送之前将其序列化。当发送一个Document对象时,Firefox 3之前的版本都是使用utf-8编码发送请求的;FireFox 3则使用由body.xmlEncoding指定的编码格式正确的发送文档,但如果未指定编码格式,则使用utf-8编码格式发送。

如果是一个nsIInputStream接口,它必须与nsIUploadChannel的setUploadStream()方法兼容。在这种情况下,将 Content-Length的头部添加到请求中,它的值则使用nsIInputStream接口的available()方法获取。任何报头包括在数据流顶部的都会被当做报文主体。所以,应该在发送请求即调用send()方法之前使用setRequestHeader() 方法设置 Content-Type头部来指定数据流的MIME类型。

发送二进制内容的最佳方法(如上传文件)是使用一个与send()方法结合的 ArrayBufferView 或者Blobs

案例: GET

const xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);

xhr.onload = function () {
   // 请求结束后,在此处写处理代码
};

xhr.send(null);
// xhr.send('string');
// xhr.send(new Blob());
// xhr.send(new Int8Array());
// xhr.send({ form: 'data' });
// xhr.send(document);

案例: POST

const xhr = new XMLHttpRequest();
xhr.open("POST", '/server', true);

//发送合适的请求头信息
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

xhr.onreadystatechange = function() {//Call a function when the state changes.
    if(xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
        // 请求结束后,在此处写处理代码
    }
}
xhr.send("foo=bar&lorem=ipsum"); 
// xhr.send('string'); 
// xhr.send(new Blob()); 
// xhr.send(new Int8Array()); 
// xhr.send({ form: 'data' }); 
// xhr.send(document);


同源策略与跨域


之前见到一个问题, 为什么表单可以跨域而AJAX不能跨域请求呢?

这是因为原页面用form提交到另一个域之后, 原页面的脚本无法获取新页面的内容, 所以浏览器认为是安全的。而AJAX是可以读取响应内容的, 因此浏览器不能允许你这样做。

这个问题就引出了我们这一段的主题, 同源策略和跨域

将跨域前要先说一下同源策略

同源策略

假设我们的域名为http://www.baidu.com:80

通常情况下我们只能在 http://www.baidu.com:80 这个域中向 http://www.baidu.com:80 这个域的其他路径发送AJAX请求, 如果我们给 http://www.qq.com:80 这个域发送AJAX请求, 浏览器就会拒绝这个请求, 这就是浏览器的同源策略

同源策略只对AJAX有效

什么样才叫同源呢?

只有协议+域名+端口一模一样的才是同源

同源
http://www.baidu.com:80/indexhttp://www.baidu.com:80/main.js 是同源

非同源


跨域

就是请求发送方向不是同源的接收方发送请求


如何发送跨域请求

  1. JSONP 可以发送跨域请求
  2. CORS跨域


CORS跨域

在后台的响应头中设置Access-Control-Allow-Origin即可, 假设我们的地址是 http://adam.com:80 我们可以告诉浏览器, 我们允许 http://frank.com:8080 这个域给我们发送跨域AJAX请求, 我们可以在响应头中这么设置

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

这样 http://frank.com:8080 这个域就可以给我们发送跨域AJAX请求了

今天的主题讲完啦, 我们下面运用今天的知识来自己封装一个AJAX

封装一个AJAX


第一版就是最简单的封装, 接收五个参数

window.jQuery = function(node){
    let nodes = {
        0: node,
        length: 1
    }
    return {
        addClass: function(){

        }
    }
}

window.jQuery.ajax = function(url, method, body, successFn, failFn) {
  let xhr = new XMLHttpRequest()
  xhr.open(method, url)
  xhr.onreadystatechange = () => {
    if(xhr.readyState === 4) {
      if(xhr.status >= 200 && xhr.status < 300) {
          successFn.call(undefined, xhr.responseText)
      }else if(xhr.status >= 400) {
        failFn.call(undefined, xhr)
      }
    }
  }
  xhr.send(body)
}

window.$ = window.jQuery

我们在第一版的基础上一行把参数改为接收一个对象

window.jQuery = function(node){
    let nodes = {
        0: node,
        length: 1
    }
    return {
        addClass: function(){

        }
    }
}
window.jQuery.ajax = function({url, method, body, successFn, failFn, headers}) {
  let xhr = new XMLHttpRequest()
  xhr.open(method, url)
  for(let key in headers) {
    xhr.setRequestHeader(key, headers[key])
  }
  xhr.onreadystatechange = () => {
    if(xhr.readyState === 4) {
      if(xhr.status >= 200 && xhr.status < 300) {
          successFn.call(undefined, xhr.responseText)
      }else if(xhr.status >= 400) {
        failFn.call(undefined, xhr)
      }
    }
  }
  xhr.send(body)
}

window.$ = window.jQuery

在第二版的基础上用promise封装一下, 让我们可以通过then来调用成功和失败后的函数

window.jQuery = function(node){
    let nodes = {
        0: node,
        length: 1
    }
    return {
        addClass: function(){

        }
    }
}

window.jQuery.ajax = function({url, method, body, headers}) {
  return new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest()

    xhr.open(method, url)
    for(let key in headers) {
      xhr.setRequestHeader(key, headers[key])
    }
    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4) {
        if(xhr.status >= 200 && xhr.status < 300) {
          resolve.call(undefined, xhr.responseText)
        }else if(xhr.status >= 400) {
          reject.call(undefined, xhr)
        }
      }
    }
    xhr.send(body)
  })
}

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

推荐阅读更多精彩内容

  • AJAX 原生js操作ajax 1.创建XMLHttpRequest对象 var xhr = new XMLHtt...
    碧玉含香阅读 3,173评论 0 7
  • Php代码 收藏代码 ajax通过 HTTP 请求加载远程数据。jQuery 底层 AJAX 实现。简单易用的高...
    Yumazhiyao阅读 927评论 0 4
  •   2005 年,Jesse James Garrett 发表了一篇在线文章,题为“Ajax: A new App...
    霜天晓阅读 883评论 0 1
  • Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest ...
    对角另一面阅读 592评论 0 1
  • 上林春令.元宵 文/蜀山倦客 凭倚凝眸冰魄,似照我、孤舟河洛。上元焰火横空,鸣响处、隐传鼓乐。 摆舟欲自岸...
    寺咀山主人阅读 280评论 7 12