• 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函数需要在分发器中脚本加载后才能执行,从而才能截取到服务器返回的数据

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

  • 相关阅读:
    RE
    【LeetCode】198. House Robber
    【LeetCode】053. Maximum Subarray
    【LeetCode】152. Maximum Product Subarray
    【LeetCode】238.Product of Array Except Self
    【LeetCode】042 Trapping Rain Water
    【LeetCode】011 Container With Most Water
    【LeetCode】004. Median of Two Sorted Arrays
    【LeetCode】454 4Sum II
    【LeetCode】259 3Sum Smaller
  • 原文地址:https://www.cnblogs.com/aaronjs/p/3790820.html
Copyright © 2020-2023  润新知