• jQuery源码分析系列(36) : Ajax


    什么是类型转化器?

    jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html

    但是浏览器的XMLHttpRequest对象对数据的响应只有 responseText与responseXML 二种

    所以现在我要定义dataType为jsonp,那么所得的最终数据是一个json的键值对,所以jQuery内部就会默认帮你完成这个转化工作

    jQuery为了处理这种执行后数据的转化,就引入了类型转化器,如果没有指定类型就依据响应头Content-Type自动处理

    数据传输,服务器只能返回字符串形式的,所以如果我们dataType为jsop或者json的时候

    服务器返回的数据为

    responseText: "{"a":1,"b":2,"c":3,"d":4,"e":5}"
    给转化成
    responseJSON: Object
        {
            a: 1
            b: 2
            c: 3
            d: 4
            e: 5
        }

    服务器的传输返回的只能是string类型的数据,但是用户如果通过jQuery的dataType定义了json的格式后,会默认把数据转换成Object的形式返回

    这就是jQuery内部做的智能处理了

    jQuery内把自定义的dataType与服务器返回的数据做相对应的映射处理,通过converters存储对应的处理句柄

    把需要类型转换器ajaxConvert在服务端响应成功后,对定义在jQuery. ajaxSettings中的converters进行遍历,找到与数据类型相匹配的转换函数,并执行。

    converters的映射:

    converters: {
               // Convert anything to text、
               // 任意内容转换为字符串
               // window.String 将会在min文件中被压缩为 a.String
               "* text": window.String,
     
               // Text to html (true = no transformation)
               // 文本转换为HTML(true表示不需要转换,直接返回)
               "text html": true,
     
               // Evaluate text as a json expression
               // 文本转换为JSON
               "text json": jQuery.parseJSON,
     
               // Parse text as xml
               // 文本转换为XML
               "text xml": jQuery.parseXML
           }

    除此之外还有额外扩展的一部分jsonp的处理

    // Ajax请求设置默认的值
    jQuery.ajaxSetup({
        /**
         * 内容类型发送请求头(Content-Type),用于通知服务器该请求需要接收何种类型的返回结果。
         * 如果accepts设置需要修改,推荐在$.ajaxSetup() 方法中设置一次。
         * @type {Object}
         */
        accepts: {
            script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
        },
        contents: {
            script: /(?:java|ecma)script/
        },
        converters: {
            "text script": function(text) {
                jQuery.globalEval(text);
                return text;
            }
        }
    });

    所以其格式就是

    text –> (html,json,script)的处理了

    其寓意就是服务器返回的用于只是string类型的文本格式,需要转化成用户想要的dataType类型的数据

    {"* text": window.String, "text html": true, "text json": jQuery.parseJSON, "text xml": jQuery.parseXML}


    类型的转化都是发生在服务器返回数据后,所以对应的就是ajax 方法中的done之后,当然这个done方法也是经过请求分发器包装过的,至于为什么要这样处理上章就已经提过到了,为了处理正常请求与jsonp的跨域请求的问题

    所以当AJAX请求完成后,会调用闭包函数done,在done中判断本次请求是否成功,如果成功就调用ajaxConvert对响应的数据进行类型转换

    所以在此之前需要:

    1:正确分配dataType类型,如果用户不设置(空)的情况

    2:需要转化成converters映射表对应的格式比如(* text, text html , text xml , text json)


    dataType类型的转化

    dataType类型的参数,可以是xml, json, script, or html 或者干脆为空,那么jQuery就需要一个只能的方法去判断当前是属于什么数据处理

    因此就引入了

    ajaxHandleResponses 处理响应转化器,解析出正确的dataType类型
    response = ajaxHandleResponses(s, jqXHR, responses);

    dataType无法就那么几种情况

    1:dataType为空,自动转化

    此时jQuery只能根据头部信息是猜测当前需要处理的类型

    // 删除掉通配dataType,得到返回的Content-Type
    while (dataTypes[0] === "*") {
        dataTypes.shift();
        if (ct === undefined) {
            ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
        }
    }

    通过xhr.getAllResponseHeaders()得到头部信息,然后去匹配Content-Type所有对象的值即可

    当然找到这个Content-Type = “html”,我们还得看看有没有对应处理的方法,如果有就需要替换这个dataTypes

    // 看看是不是我们能处理的Content-Type,比如图片这类二进制类型就不好处理了
    if (ct) {
        // 实际上能处理的就是text、xml和json
        for (type in contents) {
            if (contents[type] && contents[type].test(ct)) {
                dataTypes.unshift(type);
                break;
            }
        }
    }

    经过这个流程后,dataTypes 本来是* 就变成了对应的html了,这是jquery内部的自动转化过程


    2:dataType开发者指定

    xml, json, script, html, jsop

    总结:

    类型转换器将服务端响应的responseText或responseXML,转换为请求时指定的数据类型dataType,

    如果没有指定类型就依据响应头Content-Type自动处理


    类型转换器的执行过程
    response = ajaxConvert(s, response, jqXHR, isSuccess);

    源码部分

    function ajaxConvert(s, response, jqXHR, isSuccess) {
        current = dataTypes.shift();
        while (current) {
            if (current) {
                // 如果碰到了*号,即一个任意类型,而转换为任意类型*没有意义
                if (current === "*") {
                    current = prev;
                    // 转化的重点
                    // 如果不是任意的类型,并且找到了一个不同的类型
                } else if (prev !== "*" && prev !== current) {
    
                    // Seek a direct converter
                    // 组成映射格式,匹配转化器
                    // * text: function String() { [native code] }
                    // script json: function () {
                    // text html: true
                    // text json: function parse() { [native code] }
                    // text script: function (text) {
                    // text xml: function (data) {
                    conv = converters[prev + " " + current] || converters["* " + current];
    
                    // If none found, seek a pair
                    // 假如找不到转化器
                    // jsonp是有浏览器执行的呢,还是要调用globalEval
                    if (!conv) {
                        //...............
                    }
    
                    // Apply converter (if not an equivalence)
                    // 如果有对应的处理句柄,执行转化
                    if (conv !== true) {
    
                        // Unless errors are allowed to bubble, catch and return them
                        if (conv && s["throws"]) {
                            response = conv(response);
                        } else {
                            try {
                                //执行对应的处理句柄,传入服务器返回的数据
                                response = conv(response);
                            } catch (e) {
                                return {
                                    state: "parsererror",
                                    error: conv ? e : "No conversion from " + prev + " to " + current
                                };
                            }
                        }
                    }
                }
            }
        }
    
        return {
            state: "success",
            data: response
        };
    }

    流程

    1.遍历dataTypes中对应的处理规则【"script","json"】

    2.制作jqXHR对象的返回数据接口

    1. json: "responseJSON"
    2. text: "responseText"
    3. xml: "responseXML"

    如:jqXHR.responseText: "{"a":1,"b":2,"c":3,"d":4,"e":5}"

    3. 生成转化器对应的匹配规则,寻找合适的处理器

    4. 返回处理后的数据response


    分析一下特殊的jsonp的转化流程

    先看看转化对应的处理器

    jsonp:

    converters["script json"] = function() {
                if (!responseContainer) {
                    jQuery.error(callbackName + " was not called");
                }
                return responseContainer[0];
    };

    jsonp的转化器只是很简单的从responseContainer取出了对应的值,所以responseContainer肯定在转化之后就应该把数据给转化成数组对象了

    当然做源码分析需要一点自己想猜想能力,比如

    responseContainer这个数组对象如何而来?

    那么我们知道jsonp的处理的原理,还是通过加载script,然后服务器返回一个回调函数,responseContainer数据就是回调函数的实参

    所以需要满足responseContainer的处理,必须要先满足脚本先加载,所以我们要去分发器中找对应的加载代码

    首先responseContainer是内部变量,只有一个来源处,在预处理的时候增加一个全局的临时函数

    然后代码肯定是执行了这个函数才能把arguments参数赋给responseContainer

    overwritten = window[callbackName];
    window[callbackName] = function() {
        responseContainer = arguments;
    };

    callbcakName是内部创建的一个尼玛函数名

    jQuery203029543792246840894_1403062512436 = function() {
        responseContainer = arguments;
    };

    我们发送请求

    http://192.168.1.114/yii/demos/test.php?backfunc=jQuery203029543792246840894_1403062512436&action=aaron&_=1403062601515

    服务器那边就回调后,执行了jQuery203029543792246840894_1403062512436(responseContainer );

    所以全局的callbackName函数需要在分发器中脚本加载后才能执行,从而才能截取到服务器返回的数据

    我也不可能每个都分析到位,所以大家有选择的自己根据需求去看源码吧,大体的流程思路理解的,看起来就很快了,至于其余的类型,在之后遇到了就会在分析了

  • 相关阅读:
    multi-task learning
    代码杂谈-python函数
    代码杂谈-or符号
    安装maven
    zsh
    mint linux的几个问题
    [软件] Omnigraffle
    无梯度优化算法
    根据pdf文件获取标题等信息
    计算广告-GD广告
  • 原文地址:https://www.cnblogs.com/aaronjs/p/3790820.html
Copyright © 2020-2023  润新知