美文网首页Ajax我爱编程
JSONP和AJAX没有半毛钱关系

JSONP和AJAX没有半毛钱关系

作者: squall1744 | 来源:发表于2018-04-09 18:54 被阅读71次

    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
    

    相关文章

      网友评论

        本文标题:JSONP和AJAX没有半毛钱关系

        本文链接:https://www.haomeiwen.com/subject/izpbhftx.html