分析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 == true
async = true;

然后是setTimeout实现的异步

var async = false;
setTimeout(function(){
     console.assert(async); //没有报错
}, 0);
async = true;

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‘);

可以看见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-07 [email protected] - 返回 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。其中return response[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.js

script-transport.js

打开首先看见的依旧是初始化。然后是判断浏览器是否支持XHR2 CORS,支持的话就更换发送器,Ajax请求。这个我们这次不谈,Ajax模块有些复杂,详细解读的话篇幅太长。之后就是传统的script发送了,为script发送器添加send,_callback,abort方法。send定义io.script为require.load(用法可参照jQuery.load),url为io._getUrlForSend()。base.js在注释中有提到这个方法在methods.js中。我们待会儿再去看。之后是编码设置,还有各种状态的回调传参。_callback将io.script删除,防止重复调用,然后根据状态对io._ioReady传递对应的参数。abort顾名思义,中断函数。

总结:script-transport.js中定义了script发送器的发送和回调。

回过头去看methods.js

methods.js

开头是处理响应数据函数handleResponseData。jsonp要注意的是这些

    var prevType = dataType[0];
    for (var i = 1; i < dataType.length; i++) {
        type = dataType[i];
        var converter = converts[prevType] && converts[prevType][type];
        if (!converter) {
            throw new Error(‘no covert for ‘ + prevType + ‘ => ‘ + type);
        }
        responseData = converter(responseData);
        prevType = type;
    }
    io.responseData = responseData;

jsonp.js最后设置 dataType[0] = ‘script‘; dataType[1] = ‘json‘; 在这里转换器选定converters.script.json,然后执行将接收的json数据给io.responseData。到了这里,jsonp的数据处理就算彻底完成了。

和script没有关联。script的转换器也已经在jsonp.js中实现了。util.extend实现IO继承promise对象,并添加一些方法到原型。大多是对header的处理,而我们关心的只有_ioReady, _getUrlForSend。回调函数_ioReady根据传入的状态码和状态文本(success,error)做出响应的处理,成功就调用handleResponseData,失败就抛出。这里有个setTimeout,因为throw e会让后面的代码无法继续执行,所以需要通过异步解决这个问题。Ajax Timeout的实现也是这样的!)。_getUrlForSend就是将url对象转换为url字符串!url在这里出现啦!(kissy APIDoc中没有url.stringify,其实url.stringify = url.format,不要怀疑!我特地看了util源码的!)。

总结:methods.js负责处理响应数据以及url生成。

最后回到base.js

yiminghe菊苣在后面自立flag(// flag as sendingf! l! a! g! 看见顿时笑)!然后ransport.send()!后面的错误处理在前面已经提过了,和_ioReady一样的疑问。End.

下面是kissy io模块的简单实例,实现了对flickr API的调用(国内外都喜欢用这个测)

  <script>
          (function() {
               var APIurl = ‘http://api.flickr.com/services/feeds/photos_public.gne‘;
               var onSuccess = function (data) {
                    console.log(‘get data:‘, data);
               }
               require([‘io‘], function(IO){
                    IO({
                         url: APIurl,
                         type: ‘GET‘,
                         dataType: ‘jsonp‘,
                         jsonp: ‘jsoncallback‘,
                         jsonpCallback: ‘ctest‘,
                         data: {
                              tags: "water",
                             tagmode: "any",
                             format: "json"
                         },
                         complete: onSuccess
                    });
               });
          })()
     </script>

然后是这个程序实现的全过程示意图,让大家回顾一下前面的内容,梳理一下思路。

分析总结

优点:Chinese English与中文相结合,让注释的可读性大大增强!代码结构十分清晰,前后阅读十分顺畅,基本没有什么阻碍。在jsonp的实现上有优先尝试CORS,回调函数的处理对于我来说也是个小亮点。CommonJS规范模块加载与众不同,也让模块开发变得十分算是间接体现了阿里前端菊苣们对nodejs的强力推崇。

 缺点:类似util.parseJson、url.stringify这些在API文档未提及,导致需要去其它模块查看源代码,增加了分析耗时。这算是一个缺点吧!希望kissy的菊苣们能完善一些文档。然后就是有爱的英文注释啦~ 虽然很好玩~ 不过这也给了我给菊苣提意见的途径!(我找到了明显的单词错误,已反馈)

最后的碎碎念

这次分析的深度肯定不够,因为吾辈也就是js入门生一枚。所以希望大家发现什么写的不对,一定要告诉我啊!如果菊苣路过提点意见,那是再好不过。也希望我的博客能给想要阅读框架源码却又迟迟不敢向前的同学们,一点思路和信心(我这么菜都能看!还敢发博客!)。最后,有些同学猜的没错,这会是系列文!

时间: 2024-10-25 00:49:57

分析js框架如何实现JSONP之kissy的相关文章

jQuery源码分析系列(34) : Ajax - 预处理jsonp

上一章大概讲了前置过滤器和请求分发器的作用,这一章主要是具体分析每种对应的处理方式 $.ajax()调用不同类型的响应,被传递到成功处理函数之前,会经过不同种类的预处理(prefilters). 预处理的类型取决于由更加接近默认的Content-Type响应,但可以明确使用dataType选项进行设置.如果提供了dataType选项, 响应的Content-Type头信息将被忽略. 有效的数据类型是text, html, xml, json,jsonp,和 script. dataType:预期

使用express.js框架一步步实现基本应用以及构建可扩展的web应用

最近过年在家有点懈怠,但是自己也不断在学习新的前端技术,在家琢磨了express.js的web框架. 框架的作用就是提高开发效率,快速产出结果.即使不使用框架,我们也会在开发过程中逐渐形成构成框架. 大多数的node.js项目中都会用到express.js 目录: 一.什么是express.js框架 二.express.js是怎么工作的 三.expres.js的安装 四.express.js的脚手架 五.express.js的helloworld基本应用 一.什么是express.js框架? E

借鉴一些关于js框架的东西

八款Js框架介绍及比较,Dojo .Scriptaculous .Prototype .yui-ext .Jquery .Mochikit.mootools .moo.fx,componentartui (转载) Extjs 与 JQuery 1.Turbomail(www.turbomail.org)下一版本决定用Extjs + jquery 开发.2.JQuery 提供了方便的对网页元素操作方法,但不提供基本控件,如:Tab,Grid,Muen 等,Extjs 是一套   完整的控件库,Ex

js框架设计1.1命名空间笔记

借到了司徒正美的写的js框架设计一书,司徒大神所著有些看不太懂,果然尚需循序渐进,稳扎js基础之中. 第一张开篇司徒阐述了种子模块的概念 种子模块亦为核心模块,框架最先执行模块,司徒见解应包含:对象扩展.数组化,类型判定,简单事件的绑定和写在,无冲突处理,模块加载与domReady.应具有扩展性.常用.稳定等特点. 1.1 命名空间 一观各大框架,基本是定义一个全局变量作为命名空间,如Ext的Ext等,大体抽象取例为: if(typeof(Ten)==='undefined'){ Ten={};

JS框架比较

目前来看,JS框架以及一些开发包和库类有如下几个,Dojo .Scriptaculous .Prototype .yui-ext .Jquery .Mochikit.mootools .moo.fx Dojo (JS library and UI component ):Dojo是目前最为强大的j s框架,它在自己的Wiki上给自己下了一个定义,dojo是一个用JavaScript编写的开源的DHTML工具箱.dojo很想做一个“大一统”的 工具箱,不仅仅是浏览器层面的,野心还是很大的.Dojo

前端Js框架汇总

一.前端框架库: 1.Zepto.js 地址:http://www.css88.com/doc/zeptojs/ 描述:Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api. 如果你会用jquery,那么你也会用zepto.关于Zepto认知我也是通过与一位腾讯朋友聊天的时候知道的,只作了些基础的了解. 2.SUI Mobile 地址:http://m.sui.taobao.org 描述:SUI Mobile 是一套基于 Framework7 开

不要再使用JS框架了

原文:No more JS Frameworks byJoe Gregorio 停止编写Javascript框架吧. Javascript框架就好像死亡和税收一样:终究不可避免它的存在.我确信如果我是那面墙上的一只苍蝇,每次有人开始一个新的网页项目时,第一个问题肯定是我们用的是哪个JS框架?这就是当今业内对JS框架的根深蒂固的思维模式.但事实上并不需要如此,相反的,需要停止使用JS框架. 我们来看看我们都有些什么. Angular.Backbone和Ember,哎哟妈呀 很长一段时间内网页平台.

[IOS_HTML5]各种JS框架介绍--用HTML5/CSS3/JS开发Android/IOS应用

现在人人都想成为安卓/IOS应用开发工程师.其实,安卓/IOS应用可以用很多种语言来实现.由于我们前端开发工程师,对HTML5/CSS/JavaScript的网络编程已经相当熟悉了.所以,今天大家将会认识到一些利用前端语言来开发安卓/IOS应用的工具. 在文章的末尾,也介绍了使用JAVA.C#.Lua以及AS3来开发安卓应用的工具. 希望大家都能找到适合自己的开发工具!祝大家开发安卓/IOS应用一切顺利! PhoneGap 开发语言: HTML, CSS, JavaScript 开发工具: Ph

8款JS框架比较

Dojo     Dojo 是目前最为强大的JS框架,它在自己的 Wiki 上给自己下了一个定义,Dojo 是一个用 JavaScript 编写的开源的DHTML工具箱.Dojo 很想做一个“大一统”的工具箱,不仅仅是浏览器层面的,野心还是很大的.Dojo 包括 Ajax.Browser.Event.Widget 等跨浏览器 API,包括了 JS 本身的语言扩展,以及各个方面的工具类库,和比较完善的 UI 组件库,也被广泛应用在很多项目中,他的 UI 组件的特点是通过给 HTML 标签增加 TA