PhoneGap源码分析5——cordova/utils

在导入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: 

View Code
 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 };

其中涉及的正则表达式及其用法,在下一篇中再进行分析。

你可能感兴趣的:(PhoneGap)