在导入cordova的过程中,也即在调用cordova的工厂函数中,首先遇到的是导入另一个模块cordova/channel(注:这里由于函数声明提升,实际上是先执行工厂函数内部的其它函数声明,然后再执行下面的语句,但对这里的分析不受影响)
define("cordova", function(require, exports, module) { var channel = require('cordova/channel'); //其它代码 });
然后,我们跟踪到cordova/channel的工厂函数,可以看到,仍然需要先导入cordova/utils这个模块
define("cordova/channel", function(require, exports, module) { var utils = require('cordova/utils'); // 其它代码 });
继续跟踪,cordova/utils这个模块没有再导入其它模块,那我们就从cordova/utils的工厂函数这里开始。
先看源码(一级展开):
1 define("cordova/utils", function(require, exports, module) {//将常用的一些工具函数放在一起 2 var utils = exports;//此时exports仍旧是一个{}空对象 3 4 utils.isArray = function(a) {//判断一个对象是否为Array类型 5 }; 6 7 utils.isDate = function(d) {//判断一个对象是否为Date类型 8 }; 9 10 utils.clone = function(obj) {//深度copy一个对象,包括这个对象的事件等 11 }; 12 13 utils.close = function(context, func, params) {//对一个函数的包装调用 14 }; 15 16 utils.createUUID = function() {//产生随机字符串 17 }; 18 19 utils.extend = (function() {//继承 20 }()); 21 22 utils.alert = function(msg) {//弹出消息,不支持弹出消息时,写日志到控制台 23 }; 24 25 utils.format = function(formatString /* ,... */) {//格式化 26 }; 27 28 utils.vformat = function(formatString, args) {//格式化 29 }; 30 31 function UUIDcreatePart(length) {//内部私有函数,产生随机数 32 } 33 34 function formatted(object, formatChar) {//内部私有函数,格式化 35 } 36 });
1、首先需要说明的是,由于函数声明提升,在执行这个工厂函数时,首先执行的是UUIDcreatePart和formatted这两个内部的私有函数的声明,强调一下,只是声明函数,没有调用函数。我们顺便看一下这两个函数:
(1)UUIDcreatePart函数用来随机产生一个16进制的号码,接受一个表示号码长度的参数(实际上是最终号码长度的一半),一般用途是做为元素的唯一ID;
(2)formatted函数是用来格式化对象的,接受两个参数,被格式化的对象和格式化标志字符,也即是根据标志来格式化对象,具体的说就是传入'j','o'时序列化为JSON字符串,传入'c'时返回空字符串,其它情况返回对象的字符串形式。例如:
var object = { name:'linjisong', age:29 }; console.info(formatted(object,'j'));//{"name":"linjisong","age":29} console.info(formatted(object,'c'));//空字符串 console.info(formatted(object,'k'));//[object,object]
这个函数在内部调用了JSON.stringify这个函数,这个是内建对象JSON中的一个方法,用来将对象转变为JSON字符串,另外JSON还有一个parse方法,是将JSON字符串解析为对象的。这两个函数的签名如下:
parse(text[,reviver])
stringify(value[,replacer[,space]])
具体用法这里不再展开。
2、声明两个私有函数之后,再初始化utils,并填充一些工具函数,这里以判断是否为Array的函数为例:
utils.isArray = function(a) { return Object.prototype.toString.call(a) == '[object Array]'; };
(1)在这里不使用instanceof来判断是不是Array类型,主要是考虑到跨域或者多个frame的情况,多个frame时每个frame都会有自己的Array构造函数,从而得出不正确的结论。
(2)使用'[object Array]'来判断是根据ECMA标准中的返回值来进行的,事实上,这里不需要类型转换,而可以用全等“===”来判断。
3、utils.clone函数,针对Array和一般对象的复制,实现逻辑比较简单,内部使用递归实现,估计效率不是很好。
4、utils.close函数,封装函数的调用,将执行环境作为一个参数,调用的函数为第二个参数,调用函数本身的参数为后续参数。
5、utils.createUUID函数,调用UUIDcreatePart这个内部函数,产生一个随机的类似“bb88a05c-7c66-1355-249f-b9f60e04d368”格式的字符串。
6、utils.extend函数,是一个立即调用的匿名函数的返回值,是通过原型链实现的一个继承方法:
(1)需要清楚的是,每一个函数,都有一个属性prototype,指向这个函数的原型对象,每一个函数实例对象,也都有一个指针指向原型对象;
(2)每一个自动获取的函数原型对象,都有一个内部指针,执行函数对象本身;
(3)在访问一个实例对象属性时,会首先搜索这个对象本身,如果没有找到,会接着搜索原型对象。
(4)通过手工更改使得子类的原型对象为一个中间对象实例,而这个中间对象的原型对象指向父类原型对象,从而达到子类有一个指针指向其原型对象,子类原型对象有一个指针指向父类原型对象,因此就构成了实现继承的原型链。比如父类P的原型中有一个属性attr,子类C中没有,那么在调用C[attr]时,会先查找C中是否有attr,没有就查找C的原型对象,也就是中间对象实例,同样没有,继续查找中间对象的原型对象,因为是指向父类原型,从而可以找到并且访问attr。
参考源代码:
utils.extend = (function() { var F = function() {}; return function(Child, Parent) { F.prototype = Parent.prototype; Child.prototype = new F(); Child.__super__ = Parent.prototype; Child.prototype.constructor = Child; }; }());
7、utils.alert函数比较简单,如果alert有定义,就alert,否则的话,实现了console.log的使用这个打印日志,其他情况下什么都不做。
8、utils.format和utils.vformat是格式化函数,先看format:
utils.format = function(formatString /* ,... */) { var args = [].slice.call(arguments, 1);//传入参数中的第2个开始组成的数组 return utils.vformat(formatString, args); };
这里调用了空数组的slice函数,类似的函数有:
(1)push():接受任意数量的参数,逐个添加到数组末尾,并返回修改后数组长度
(2)pop():移除最后一项,修改数组长度,返回被移除的项
(3)shift():移除数组第一项,修改数组长度,返回被移除的项
(4)unshift():在数组前端添加任意个项,并返回数组长度
(5)slice():基于当前数组的一个或多个项创建一个新数组,可以接受1或2个参数,即要返回项的起始和结束位置,只有一个参数时,返回从该参数指定位置开始到末尾,有两个参数,返回这两个参数之间项(前闭后开区间),slice不会影响原数组。若参数为负数,则用数组长度加上参数直至为正数,若结束位置小于起始位置,返回空数组。
(6)splice():返回从原数组中删除的项构成的数组,若没有删除,返回空数组
- 删除:可以删除任意数量的项,只需指定2个参数,要删除的第一项的位置和要删除的项数,如splice(0,2)会删除前面两项
- 插入:可以向指定位置插入任意数量的项,只需提供3个参数,起始位置,0(要删除的数),要插入的项,如果要插入多个项,可以传入第四、第五以至任意多个项
- 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项
再看vformat:
1 utils.vformat = function(formatString, args) { 2 if (formatString === null || formatString === undefined) return "";//源对象为null或undefined时,直接返回空字符串 3 if (arguments.length == 1) return formatString.toString();//如果args未传入,直接返回源对象的字符串表示 4 if (typeof formatString != "string") return formatString.toString();//源对象不是字符串时,直接返回字符串表示 5 6 var pattern = /(.*?)%(.)(.*)/; 7 var rest = formatString; 8 var result = []; 9 10 while (args.length) {//循环处理,每处理一次,args会减少一项,直至args.length为0,这里个人并不推荐这种用法,因为每次循环都要计算长度 11 var arg = args.shift();//移除数组的第一项,返回被移除的项,修改数组长度 12 var match = pattern.exec(rest); 13 14 if (!match) break; 15 16 rest = match[3]; 17 18 result.push(match[1]); 19 20 if (match[2] == '%') { 21 result.push('%'); 22 args.unshift(arg); 23 continue; 24 } 25 26 result.push(formatted(arg, match[2])); 27 } 28 29 result.push(rest); 30 31 return result.join(''); 32 };
其中涉及的正则表达式及其用法,在下一篇中再进行分析。