jQuery源码分析系列(35) : Ajax - jsonp的实现与原理

简介:

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();
                }
            }
        };
    }
});
复制代码

此时就很明了吧

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


本文转自艾伦 Aaron博客园博客,原文链接:http://www.cnblogs.com/aaronjs/p/3785646.html,如需转载请自行联系原作者

相关文章
N..
|
6月前
|
XML JSON 前端开发
jQuery实现Ajax
jQuery实现Ajax
N..
68 1
|
6月前
|
XML 前端开发 JavaScript
jQuery中ajax如何使用
jQuery中ajax如何使用
70 0
|
6月前
|
XML 前端开发 JavaScript
ajax原理是什么?如何实现?
ajax原理是什么?如何实现?
91 0
|
5月前
|
前端开发 JavaScript
杨校老师课堂之基于Servlet整合JQuery中的Ajax进行表单提交[基于IDEA]
杨校老师课堂之基于Servlet整合JQuery中的Ajax进行表单提交[基于IDEA]
46 0
杨校老师课堂之基于Servlet整合JQuery中的Ajax进行表单提交[基于IDEA]
|
2月前
|
XML 前端开发 JavaScript
ajax运行原理
AJAX(Asynchronous JavaScript and XML)是一种使网页能够在不重新加载整个页面的情况下与服务器异步通信的技术。尽管名字中有“XML”,但它支持多种数据格式,如JSON、HTML和纯文本。用户行为触发AJAX请求后,通过创建`XMLHttpRequest`对象或使用现代浏览器中的`fetch` API来配置并发送HTTP请求。请求完成后,可以通过回调函数处理服务器返回的数据,更新网页内容。现代JavaScript推荐使用更简洁强大的`fetch` API来实现AJAX功能,广泛应用于动态网页和单页应用中,提升用户体验。
|
3月前
|
XML JSON 前端开发
AJAX是什么?原生语法格式?jQuery提供分装好的AJAX有什么区别?
AJAX是什么?原生语法格式?jQuery提供分装好的AJAX有什么区别?
34 0
|
3月前
|
JavaScript 前端开发
Ajax的使用(jquery的下载)
这篇文章是关于Ajax学习笔记的分享,包括JQuery的下载方式、Ajax的主要参数说明,以及如何在网页中使用Ajax进行异步请求的示例代码。
|
5月前
|
JavaScript 前端开发 安全
安全开发-JS应用&原生开发&JQuery库&Ajax技术&加密编码库&断点调试&逆向分析&元素属性操作
安全开发-JS应用&原生开发&JQuery库&Ajax技术&加密编码库&断点调试&逆向分析&元素属性操作
|
4月前
|
前端开发 JavaScript API
js【详解】ajax (含XMLHttpRequest、 同源策略、跨域、JSONP)
js【详解】ajax (含XMLHttpRequest、 同源策略、跨域、JSONP)
54 0
|
6月前
|
前端开发 JavaScript PHP
【PHP开发专栏】jQuery与PHP实现Ajax通信
【4月更文挑战第30天】本文介绍了使用jQuery和PHP实现Ajax通信的步骤。首先,讲解了Ajax的基础和jQuery简化Ajax操作的概念。接着,展示了如何使用jQuery的`$.get()`、`$.post()`和`$.ajax()`方法发送GET和POST请求,以及如何控制请求细节。在PHP端,讨论了接收和响应Ajax请求的方法,包括处理数据、设置响应类型和错误处理。结合jQuery与PHP,开发者能实现高效、无缝的异步数据传输,提升Web应用的用户体验。
126 1