PhoneGap源码分析7——cordova/channel
分析完了cordova/utils之后,回到cordova/channel这个模块来,这个模块是实现事件监听的基础,当然,我们的焦点是它的构造函数,源码中是匿名的,这里为了行文方便,姑且称之为factory。
要分析一个函数,从外部来说,知道怎么调用它就行了,这也就是通常所说的暴露在外的API,我们知道,factory是作为一个参数来传递给define函数的,并在第一次require中实际调用的,之后就清除了这个构造函数,回过头来看看这个调用的代码:
1 function build(module) { 2 var factory = module.factory; 3 module.exports = {}; 4 delete module.factory; 5 factory(require, module.exports, module); 6 return module.exports; 7 }
实际调用在第5行,传入三个参数,没有使用返回值,调用之后返回的是module.exports,而这正是其中一个传入参数,我们可以由此判断,在factory这个函数内部,module.exports被修饰,因此,下面在分析factory时,需要重点关注module.exports这个参数(channel)的变更。
从外部调用角度来分析函数,虽然也是一个不错的方法,尤其是对不关注具体实现的时候,但是,也可能会有以偏概全的不利影响,毕竟,很多时候外部调用很难穷尽所有用法。现在,我们再从内部代码来分析一下factory。
1 function(require, exports, module) { 2 var utils = require('cordova/utils'); 3 4 var Channel = function(type, opts) { 5 }, 6 channel = { 7 }; 8 9 function forceFunction(f) { 10 } 11 12 Channel.prototype.subscribe = function(f, c, g) { 13 }; 14 15 Channel.prototype.subscribeOnce = function(f, c) { 16 }; 17 18 Channel.prototype.unsubscribe = function(g) { 19 }; 20 21 Channel.prototype.fire = function(e) { 22 }; 23 24 channel.create('onDOMContentLoaded'); 25 26 channel.create('onNativeReady'); 27 28 channel.create('onCordovaReady'); 29 30 channel.create('onCordovaInfoReady'); 31 32 channel.create('onCordovaConnectionReady'); 33 34 channel.create('onDeviceReady'); 35 36 channel.create('onResume'); 37 38 channel.create('onPause'); 39 40 channel.create('onDestroy'); 41 42 channel.waitForInitialization('onCordovaReady'); 43 channel.waitForInitialization('onCordovaConnectionReady'); 44 45 module.exports = channel; 46 47 }
1、从返回值上看,这里没有明确的返回,但是根据前面的分析,我们知道,第45行的module.exports = channel;就相当于是将channel这个变量作为返回值返回了。
2、从结构上看,这个构造函数,先是定义了三个内部变量utils、Channel、channel,然后是修改Channel的原型,接着使用channel创建一系列事件并最终返回。
3、再从动态执行的角度来看一下:
(1)当然,由于函数声明提升,首先是定义第9行开始的函数forceFunction:
function forceFunction(f) { if (f === null || f === undefined || typeof f != 'function') throw "Function required as first argument!"; }
这个函数很简单,就是判断传入的参数f是不是一个函数,如果不是就抛出异常。这里稍微提一下等号“==”和全等号“===”的区别,等号在比较之前,如果需要,会自动转换数据类型,而全等号不会自动转换类型,比如'2'==2而'2' !==2。
(2)第2-7行是定义三个内部变量并初始化,utils就是前面两篇文章分析的工具模块;Channel是一个普通的构造器方法;channel则是最终返回的结果,是通过对象字面量定义的。
4、将Channel的定义及其原型的修改放在一起,我们可以看到一个典型的创建对象的方法:通过构造器初始化内部变量,从而让各个实例互相独立,通过修改函数原型共享实例方法。原型中定义了subscribe、unsubscribe、subscribeOnce、fire四个方法:
(1)、subscribe(向事件通道注入事件处理函数)
1 Channel.prototype.subscribe = function(f, c, g) { 2 // need a function to call 3 forceFunction(f); 4 5 var func = f; 6 if (typeof c == "object") { func = utils.close(c, f); } 7 8 g = g || func.observer_guid || f.observer_guid; 9 if (!g) { 10 // first time we've seen this subscriber 11 g = this.guid++; 12 } 13 else { 14 // subscriber already handled; dont set it twice 15 return g; 16 } 17 func.observer_guid = g; 18 f.observer_guid = g; 19 this.handlers[g] = func; 20 this.numHandlers++; 21 if (this.events.onSubscribe) this.events.onSubscribe.call(this); 22 if (this.fired) func.call(this); 23 return g; 24 };
subscribe是向通道注入事件处理函数并返回一个自增长的ID(可以用来反注入或内部查找),多次注入会直接返回。在第8行有一个用法:g = g || func.observer_guid || f.observer_guid,也就是取第一个为true的值(null、undefined、false、0都视为false),第一次会将函数注入通道,并且触发注入事件(如果有),然后如果需要立即触发,则再触发注入的函数。
(2)unsubscribe(解除事件处理函数,反注入)
Channel.prototype.unsubscribe = function(g) { // need a function to unsubscribe if (g === null || g === undefined) { throw "You must pass _something_ into Channel.unsubscribe"; } if (typeof g == 'function') { g = g.observer_guid; } var handler = this.handlers[g]; if (handler) { if (handler.observer_guid) handler.observer_guid=null; this.handlers[g] = null; delete this.handlers[g]; this.numHandlers--; if (this.events.onUnsubscribe) this.events.onUnsubscribe.call(this); } };
反注入比较简单,就是将前面注入的处理函数给删除,并且触发反注入事件(如果有),可以传入注入的函数,或者传入注入函数时返回的ID。
(3)subscribeOnce(注入事件处理,但是只调用一次,然后就自动解除和之间的关联关系)
Channel.prototype.subscribeOnce = function(f, c) { // need a function to call forceFunction(f); var g = null; var _this = this; var m = function() { f.apply(c || null, arguments); _this.unsubscribe(g); }; if (this.fired) {//立即触发,就直接调用 if (typeof c == "object") { f = utils.close(c, f); } f.apply(this, this.fireArgs); } else {//注入“调用并反注入”的函数 g = this.subscribe(m); } return g; };
注入只调用一次的函数,如果需要立即触发,实际上只需要触发而不需要注入,不需要立即触发,将原函数调用和反注入原函数作为一个新的事件处理函数注入。
(4)fire(触发所有注入的函数)
Channel.prototype.fire = function(e) { if (this.enabled) { var fail = false; this.fired = true; for (var item in this.handlers) { var handler = this.handlers[item]; if (typeof handler == 'function') { var rv = (handler.apply(this, arguments)===false); fail = fail || rv; } } this.fireArgs = arguments; return !fail; } return true; };
就是在当前状态可用的情况下调用所有注入的事件处理函数。
在这里复习一下函数调用的两种方式:
A、使用括号调用:functionName(args);
B、使用函数实例方法apply或call方法调用:如functionName.apply(scope,args)或functionName.call(scope, arg1, arg2,...,argn)。这里关键的是scope可用改变functionName这个函数的作用域。而apply和call的区别就是后面的参数,前者传入数组或类数组(如arguments),后者需要将参数一一列举。
5、返回值channel
先是通过对象字面量定义,然后创建一系列事件,注意的是,Channel的subscribe方法是同一个事件的多次处理方法,而这里创建的则是初始启动的一系列事件。这些事件分别是:onDOMContentLoaded DOM文档已经加载并解析
onNativeReady Cordova本地已经就绪
onCordovaReady Cordova中所有Javascript对象已经创建完毕,开始运行插件
onCordovaInfoReady 设备属性可以访问
onCordovaConnectionReady Cordova连接已经设置好
onDeviceReady Cordova已经加载完成
onResume 启动/恢复
onPause 暂停
onDestroy 应用销毁(通常会使用window.onload)
郴江幸自绕郴山,为谁流下潇湘去?
欲将心事付瑶琴,知音少,弦断有谁听?
倩何人,唤取红巾翠袖,揾英雄泪!
零落成泥碾作尘,只有香如故!