首页 > 网站 > WEB开发 > 正文

分析js框架如何实现JSONP之kissy

2024-04-27 14:17:36
字体:
来源:转载
供稿:网友

分析js框架如何实现JSONP之kissy

开始前的准备

JSONP原理简介(知晓的同学就当复习一下 同源策略的限制让程序员想到了利用不受同源策略影响的<script>进行跨域请求。而单纯的JSON数据仅仅只是数据,被<script>加载入页面没有任何意义,因此需要一个变量作为函数名,也就是那个“P”,然后JSON数据作为函数参数传递过来。之后当浏览器加载完成后,函数执行。因此这个函数必须是个动态创建的全局变量。而JSONP其实就是动态加载js脚本。要传递变量给后端,我们需要一个参数,常用有jsonp,jsonpCallback,callback,当然这都需要后端配合。另外,XHR2 CORS支持跨域,可以看看这里js库Ajax模块实现过程简介 总体分为三部分,XHR对象、传送器transport、转换器converters。首先是创建一个XHR对象,也可能会是一个程序员为了在各浏览器中实现XHR2大部分接口而创建的伪XHR,但不管怎么说,开始必然是先创建一个XHR对象。之后以form或script形式向服务器端发送请求,这里就有一个传送器的概念,它负责发送请求,视情况采用不同的传送器。之后我们需要将返回的数据转换为程序员所需的形式,这就需要一个转换器。kissy框架相关知识(了解一下就差不多了 util.js: kissy的一个模块,可能叫utils更加合适,它主要提供一些kissy框架常用的辅助函数。辅助函数对于一个框架而言十分重要,像each,filter,isArray这些常见的辅助函数。还有就是和这个博客相关的util.parseJson,我觉得十分重要,不知道为什么kissy 5.0官方提供的API中没有这个,但是我在github的源码是找到了的,util/lib/util/json.js 这里就可以看见。下文中常用的util.mix除了很实用以外还很有趣,它可以添加白名单,被复制对象的属性在白名单内才会被复制,underscore也有个类似参数是做黑名单用的。(吾辈认为因为使用场景的不同,两种方式的效率高低也会不同,所以util这个应该不是什么改进,纯粹是为了和underscore不一样(如果是我错了,请大家留言轻点教育... event-custom.js:自定义事件在jQuery这个业界巨头手中发光发热(jQuery.on),kissy自然也要有。就是event-custom这个kissy模块。它用的是PubSub模式(其实就是观察者模式),一个自定义事件类型发布(Pub)给多个负责事件处理的回调函数订阅(Sub),触发事件的时候在进行回调。PubSub模式用来处理这样的异步事件,但它和异步没有关系,它是同步的,自定义事件也并不会进入js的Event Loop。node十分喜爱这个模式,events的EventEmitter对象用的就是PubSub模式,我用它做了简单的测试。
var events = require("events");var emitter = new events.EventEmitter();var async = false;emitter.on('eventType', function(){     console.assert(async);});emitter.emit('eventType'); //报错false == trueasync = true;
View Code

然后是setTimeout实现的异步

var async = false;setTimeout(function(){     console.assert(async); //没有报错}, 0);async = true;
View Code PRomise:kissy的promise模块用的是Promise/A+规范,它是对Promise/A的迭代版。promise可以让那些富含Ajax的页面拜托回调的金字塔噩梦,也可以让js程序员轻松的使用起异步来~ 具体就不在这里讲述了。正文 首先我们要明确jsonp实现的几个步骤,简介中也提过,首先生成URL,然后生成一个scirpt标签发送请求,接着将数据转换出来,最后回调。 那接下来,我来一步步看看kissy jsonp实现的代码 直接上 IO.jsonp( url, data, callback ) 我们在github找到kissy IO模块,首先是io.js,可以看见这些CommonJS风格的
var serializer = require('./io/form-serializer');var IO = require('./io/base');var util = require('util');require('./io/xhr-transport');require('./io/script-transport');require('./io/jsonp');require('./io/form');require('./io/iframe-transport');require('./io/methods');
View Code

可以看见xhr-transport,script-transport,iframe-transport这是在加载各种传送器,我们需要注意的是script-transport,jsonp会用到它。base.js是IO模块的基础。后面我们还会去看。 这个API自然调用的是IO,一如jQuery.get其实就是jQuery.ajax的简写。具体看它如何执行,
jsonp: function (url, data, callback) {       if (typeof data === 'function') {         callback = data;         data = undefined;       }       return get(url, data, callback, 'jsonp')}

getScript: require.load

function get(url, data, callback, dataType, type) {// data 参数可省略if (typeof data === 'function') {    dataType = callback;    callback = data;    data = undefined;}return IO({    type: type || 'get',    url: url,    data: data,    complete: callback,    dataType: dataType});}

意料之中的调用IO,dataType设置为jsonp,get请求。其它参数都和一般ajax参数一致,dataType显然线索啦。特地在getScript:require.load留了心,因为getScript负责script发送工作,而jsonp它本身也是以一种getScript。var IO = require('./base');那就去base.js中看看总结:IO.js作用就是将各个js文件载入,接着给IO添加几个便捷入口,最后统一调用IO函数。base.js 首先我们肯定会被defaultConfig吸引目光,因为这里设置了Ajax所需的各种默认配置,HTTP请求mothods、MIME类型、编码等,一般的转换器设置也都在这个对象了。 接着是设置配置的函数setUpConfig(c),在我们不知道会传入什么参数前还是不看了(其实看看也可以,看看代码也能猜个八九不离十)。 那么直接找到IO函数,参数是c。self就是IO函数的一个实例。然后给IO套上promise模式,可以用起链式操作来啦~(我也是看yiminghe菊苣的注释乐的! ‘2012-2-07yiminghe@Gmail.com- 返回 Promise 类型对象,可以链式操作啦!’卧槽....好萌!)。self.userConfig = c;c = setUpConfig(c); 我们的参数现在传给了setConfig并生成新的参数对象。为了不中断对IO函数的分析。我们先往后分析。util.mix是将后面的对象clone到self,然后就添加了一些诸如config,transport,timeoutTimer一堆属性。再往后,声明传送器构造函数、传送器变量,然后触发start事件!这标志着IO的开始!后面接着是根据dataType选择对应的传送器并创建实例。然后是Ajax相关的Header、readyState、status等设置,这和jsonp没有关系。然后它用setTimeout模仿了Timeout功能。再往后也没什么了。base.js的最后我们又往IO函数添加了像setupConfig,setupTransport,getTransport这些方法,来实现config设置等功能。而我们在意的是setupTransport,传送器设置。 现在我们回过头来看setupConfig。它的返回值是一个新的config对象。在一开始用把参数c深度clone到defaultConfig,结合之后产生所需的config赋值给c,像这样

     var context = c.context;     delete c.context;     c = util.mix(util.clone(defaultConfig), c, {     deep: true     });     c.context = context || c;

context代表的是请求后回调函数的“环境”(context傻瓜翻译)——success,error,complete。后面声明变量什么的略过,uri = c.uri,是一个包含url所有相关参数的url对象。uri.query由url中querystring的名/值对构成JSON格式对象/数组。我们排除与跨域无关的处理过程,jsonp大多都是没有的啊~

dataType = c.dataType = util.trim(dataType || '*').split(rspace);if (!('cache' in c) && util.inArray(dataType[0], ['script', 'jsonp'])) {c.cache = false;}

util.trim去掉dataType前后空格,rspace = //s+/,split按空格分割dataType成子字符串数组。接着确认dataType为jsonp。c.cache标识是否是调用script或jsonp发送器,false即代表是.它的作用:url会添加uri.query._ksTS = util.now() + '_' + util.guid(); 也就是 ksTS=时间戳_全局唯一标示符,guid函数的作用就是生成一个全局唯一标示符,英文一般叫UUID或是GUID。到这里,url处理就完成了。现在还是一个对象。接着就start! start事件有很多事件处理器,它们在执行前都会有判断,我们手持dataType['jsonp']开始jsonp.js部分。

jsonp.js首先需要注意的是
IO.setupConfig({    jsonp: 'callback',    jsonpCallback: function () {    // 不使用 now() ,极端情况下可能重复    return util.guid('jsonp');    }});
调用了setupConfig,显然函数参数名初始化为callback。jsonpCallback是初始化一个全局唯一标识符的函数。后面util.guid是生成全局唯一标示符,加在'jsonp'后面作为函数名,而全局唯一可以防止这个函数名和其它变量重名。后面start事件的回调函数中首先生成函数名字符串,然后纳入uri.query中c.uri.query[c.jsonp]=jsonpCallback;。之后函数执行了jsonp常见的一系列程序。创建一个全局函数 ,绑定在全局对象 win[ jsonpCallback ],并设置请求完成后清除 delete win[ jsonpCallback ];。这里要注意的是要绑定在全局对象上,因为直接var声明的全局变量是无法清除的。而kissy在这儿用了一个小技巧,它创建一个临时的全局函数给了一个叫previous的变量,然后在请求结束后回调函数时,win[ jsonpCallback ] =previous 。如果成功加载的话,就会执行previous将所需的json格式数据保存起来。之后定义的是转换器converters.script.json。其中returnresponse[0], 返回值在之前已经保存response=[r],[r]为全局函数的参数,转换器就这样实现了!而失败也有相对应处理机制throw new Error('not call jsonpCallback: ' + jsonpCallback);。最后将dataType修改为 dataType[0] = 'script';dataType[1] = 'json'; 结合注释可以轻而易举地知道,这是在给调用script发送器做准备呢。总结:jsonp.js中定义了,添加URL中全局函数参数名/值,全局函数定义、调用、删除,script到json转换器实现这一系列的行为,并绑定在了start事件。我们回到base.js,下面就是准备发送了。TransportConstructor = transports[c.dataType[0]] || transports['*'];transport = new TransportConstructor(self);之前jsonp.js中已经知道了最后会是script传送器负责发送请求。其实jsonp和getScript发送请求的原理都是一样的,动态加载js脚本,所以最后统一使用script传送器一点不奇怪。而IO.setupTransport工作早在IO.js加载*-transport.js的时候就已经完成了,像这样IO.setupTransport('script', ScriptTransport);这样IO.setupTransport('iframe', IframeTransport);(不要问我怎么知道的,到了发送器部分了,我当然会打开看看啦~)。看一下setupTransport啦
dataType = c.dataType = util.trim(dataType || '*').split(rspace);if (!('cache' in c) && util.inArray(dataType[0], ['script', 'jsonp'])) {c.cache = false;}

之后显然就是打开script-transport.jsscript-transport.js打开首先看见的依旧是初始化。然后是判断浏览器是否支持XHR2 CORS,支持的话就更换发送器,Ajax请求。这个我们这次不谈,Ajax模块有些复杂,详细解读的话篇幅太长。之后就是传统的s
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表