zoukankan      html  css  js  c++  java
  • jQuery源码分析系列(35) : Ajax

    ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本

    json核心就是:允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

    jquery ext dojo这类库的实现手段其实大同小异

    在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但img、iframe、script等标签是个例外,这些标签可以通过src属性请求到其他服务器上的数据。

    利用script标签的开放策略,我们可以实现跨域请求数据,当然,也需要服务端的配合。

    先看一段jQuery处理jsonp的情况

    通过发送php请求

    客户端

    $.ajax({
        async: false, // 同步加载数据,即等到ajax执行完毕再接着执行下面的语句
        url: 'http://192.168.1.114/yii/demos/test.php', //不同的域
        type: 'GET', // jsonp模式只有GET是合法的
        data: {
            'action': 'aaron'
        }, // 预传参的数组
        dataType: 'jsonp', // 数据类型
        jsonp: 'backfunc', // 指定回调函数名,与服务器端接收的一致,并回传回来
        success: function(json) {
            console.log(json);
        }
    })

    php服务端

    <?php
    $act = trim($_GET['action']);
    
    if($act == 'aaron' ){
        echo trim($_GET['backfunc']).'('. json_encode(array('status'=>1,'info'=>'OK')) .')';  
    }
    ?>

    一般的ajax是不能跨域请求的,因此需要使用一种特别的方式来实现跨域,其中的原理是利用 <script> 元素的这个开放策略

    这里有2个重要的参数

    • jsonpCallback

    为jsonp请求指定一个回调函数名。这个值将用来取代jQuery自动生成的随机函数名。这主要用来让jQuery生成一个独特的函数名,这样管理请求更容易,也能方便地提供回调函数和错误处理。你也可以在想让浏览器缓存GET请求的时候,指定这个回调函数名。从jQuery 1.5开始,你也可以使用一个函数作为该参数设置,在这种情况下,该函数的返回值就是jsonpCallback的结果。

    • jsonp

    在一个jsonp请求中重写回调函数的名字。这个值用来替代在"callback=?"这种GET或POST请求中URL参数里的"callback"部分,比如{jsonp:'onJsonPLoad'}会导致将"onJsonPLoad=?"传给服务器。在jQuery 1.5,,设置jsonp选项为false,阻止了jQuery从加入"?callback"字符串的URL或试图使用"=?"转换。在这种情况下,你也应该明确设置jsonpCallback设置。例如, { jsonp: false, jsonpCallback: "callbackName" }、


    当我们正常地请求一个JSON数据的时候,服务端返回的是一串JSON类型的数据,而我们使用JSONP模式来请求数据的时候

    服务端返回的是一段可执行的JavaScript代码

    所以我们可见服务器代码最后一行

    $_GET['backfunc']).'('. json_encode(array('status'=>1,'info'=>'OK')) .')

    就是执行的 backfunc方法,然后把数据通过回调的方式传递过去

    OK,就是整个流程就是:

    客户端发送一个请求,规定一个可执行的函数名(这里就是jQuery做了封装的处理,自动帮你生成回调函数并把数据取出来供success属性方法来调用,不是传递的一个回调句柄),服务端接受了这个backfunc函数名,然后把数据通过实参的形式发送出去


    jQuery的实现:

    通过ajax请求不同域的实现,底层不是靠XmlHttpRequest而是script,所以不要被这个方法给迷惑了

    在ajax请求中类型如果是type是get post,其实内部都只会用get,因为其跨域的原理就是用的动态加载script的src,所以我们只能把参数通过url的方式传递

    比如

    $.ajax({
        url: 'http://192.168.1.114/yii/demos/test.php', //不同的域
        type: 'GET', // jsonp模式只有GET是合法的
        data: {
            'action': 'aaron'
        }, // 预传参的数组
        dataType: 'jsonp', // 数据类型
        jsonp: 'backfunc', // 指定回调函数名,与服务器端接收的一致,并回传回来
    })

    其实jquery内部会转化成

    http://192.168.1.114/yii/demos/test.php?backfunc=jQuery2030038573939353227615_1402643146875&action=aaron

    然后动态加载

    <script type="text/javascript" src="http://192.168.1.114/yii/demos/test.php?backfunc=jQuery2030038573939353227615_1402643146875&action=aaron"></script>

    然后php方就会执行backfunc(传递参数);

    所以流程就会分二步:

    1:针对jsonp的预处理,主要是转化拼接这些参数,然后处理缓存,因为jsonp的方式也是靠加载script所以要关闭浏览器缓存

    inspectPrefiltersOrTransports中,当作了jsonp的预处理后,还要在执行inspect(dataTypeOrTransport);的递归,就是为了关闭这个缓存机制

    var dataTypeOrTransport = prefilterOrFactory(options, originalOptions, jqXHR);
                /**
                 * 针对jonsp处理
                 */
                if (typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[dataTypeOrTransport]) {
                    //增加cache设置标记
                    //不需要缓存
                    //dataTypes: Array[2]
                    // 0: "script"
                    // 1: "json"
                    options.dataTypes.unshift(dataTypeOrTransport);
                    inspect(dataTypeOrTransport);
                    return false;
                } else if (seekingTransport) {
                    return !(selected = dataTypeOrTransport);
                }

    具体的预处理的代码

    // Detect, normalize options and install callbacks for jsonp requests
    // 向前置过滤器对象中添加特定类型的过滤器
    // 添加的过滤器将格式化参数,并且为jsonp请求增加callbacks
    jQuery.ajaxPrefilter("json jsonp", function(s, originalSettings, jqXHR) {
    
        var callbackName,
            overwritten,
            responseContainer,
            // 如果是表单提交,则需要检查数据
            jsonProp = s.jsonp !== false && (rjsonp.test(s.url) ?
                "url" :
                typeof s.data === "string" 
                && !(s.contentType || "").indexOf("application/x-www-form-urlencoded") 
                && rjsonp.test(s.data) && "data"
            );
    
        // Handle iff the expected data type is "jsonp" or we have a parameter to set
        // 这个方法只处理jsonp,如果json的url或data有jsonp的特征,会被当成jsonp处理
        if (jsonProp || s.dataTypes[0] === "jsonp") {
    
            // Get callback name, remembering preexisting value associated with it
            // s.jsonpCallback时函数,则执行函数用返回值做为回调函数名
            callbackName = s.jsonpCallback = jQuery.isFunction(s.jsonpCallback) ?
                s.jsonpCallback() :
                s.jsonpCallback;
    
            // Insert callback into url or form data
            // 插入回调url或表单数据
            // "test.php?symbol=IBM&callback=jQuery20309245402452070266_1402451299022"
            if (jsonProp) {
                s[jsonProp] = s[jsonProp].replace(rjsonp, "$1" + callbackName);
            } else if (s.jsonp !== false) {
                s.url += (ajax_rquery.test(s.url) ? "&" : "?") + s.jsonp + "=" + callbackName;
            }
    
            // Use data converter to retrieve json after script execution
            s.converters["script json"] = function() {
                if (!responseContainer) {
                    jQuery.error(callbackName + " was not called");
                }
                return responseContainer[0];
            };
    
            // force json dataType
            // 强制跟换类型
            s.dataTypes[0] = "json";
    
            // Install callback
            // 增加一个全局的临时函数
            overwritten = window[callbackName];
            window[callbackName] = function() {
                responseContainer = arguments;
            };
    
            // Clean-up function (fires after converters)
            // 在代码执行完毕后清理这个全部函数
            jqXHR.always(function() {
                // Restore preexisting value
                window[callbackName] = overwritten;
    
                // Save back as free
                if (s[callbackName]) {
                    // make sure that re-using the options doesn't screw things around
                    s.jsonpCallback = originalSettings.jsonpCallback;
    
                    // save the callback name for future use
                    oldCallbacks.push(callbackName);
                }
    
                // Call if it was a function and we have a response
                if (responseContainer && jQuery.isFunction(overwritten)) {
                    overwritten(responseContainer[0]);
                }
    
                responseContainer = overwritten = undefined;
            });
    
            // Delegate to script
            return "script";
        }
    });

     jquery会在window对象中加载一个全局的函数,当代码插入时函数执行,执行完毕后就会被移除。同时jquery还对非跨域的请求进行了优化,如果这个请求是在同一个域名下那么他就会像正常的Ajax请求一样工作。

    分发器执行代码:

    当我们所有的参数都转化好了,此时会经过请求发送器用来处理发送的具体

    为什么会叫做分发器,因为发送的请求目标

    ajax因为参杂了jsonp的处理,所以实际上的请求不是通过 xhr.send(XmlHttpRequest)发送的

    而是通过get方式的脚本加载的

    所以

    transports对象在初始化构件的时候,会生成2个处理器

    1. *: Array[1]        针对xhr方式
    2. script: Array[1]  针对script,jsonp方式

    所以

    transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR);

    那么得到的transport就会根据当前的处理的类型,来选择采用哪种发送器(*、script)

    针对script的请求器

    jQuery.ajaxTransport("script", function(s) {
        // This transport only deals with cross domain requests
        if (s.crossDomain) {
            var script, callback;
            return {
                send: function(_, complete) {
                    script = jQuery("<script>").prop({
                        async: true,
                        charset: s.scriptCharset,
                        //"http://192.168.1.114/yii/demos/test.php?backfunc=jQuery20308569577629677951_1402642881663&action=aaron&_=1402642881664"
                        src: s.url
                    }).on(
                        "load error",
                        callback = function(evt) {
                            script.remove();
                            callback = null;
                            if (evt) {
                                complete(evt.type === "error" ? 404 : 200, evt.type);
                            }
                        }
                    );
                    document.head.appendChild(script[0]);
                },
                abort: function() {
                    if (callback) {
                        callback();
                    }
                }
            };
        }
    });

    此时就很明了吧

    所以最终的实现就是通过动态加载脚本!

  • 相关阅读:
    LeetCode偶尔一题 —— 617. 合并二叉树
    《剑指offer》 —— 链表中倒数第k个节点
    《剑指offer》 —— 青蛙跳台阶问题
    《剑指offer》—— 二维数组中的查找
    《剑指offer》—— 替换空格
    《剑指offer》—— 合并两个排序的链表
    《剑指offer》—— 礼物的最大价值
    生成Nuget 源代码包来重用你的Asp.net MVC代码
    Pro ASP.Net Core MVC 6th 第四章
    Pro ASP.NET Core MVC 6th 第三章
  • 原文地址:https://www.cnblogs.com/aaronjs/p/3785646.html
Copyright © 2011-2022 走看看