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

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

拣尽寒枝不肯栖,寂寞沙洲冷。
郴江幸自绕郴山,为谁流下潇湘去?
欲将心事付瑶琴,知音少,弦断有谁听?
倩何人,唤取红巾翠袖,揾英雄泪!
零落成泥碾作尘,只有香如故!
原文地址:https://www.cnblogs.com/linjisong/p/2630049.html