这一篇我们分析cordova/builder这个模块。
在具体看这个模块之前,先复习一下Object类型。
(1)Object类型是所有它的实例的基础,所有的内置类型都是通过原型继承的方式继承了Object类。
(2)Object的每个实例都有一个Constructor属性,指向创建这个实例的函数。
(3)Object的每个实例都有下面的方法:
A、hasOwnProperty(propertyName):检查propertyName是否在当前实例中(在实例原型中同样返回false)。
B、propertyIsEnumerable(propertyName):检查propertyName是否可以使用for-in语句访问(只要属性没有特别标识,在实例原型中的属性也可以访问)。
C、isPorpertyOf(object):检查object是否为另一个对象的原型。
D、toLocalString()、toString()、valueOf()。
因此可以使用下面的结构来遍历实例中的属性:
for(var propertyName in object){ if(object.hasOwnPorperty(propertyName)){ //循环处理 } }
现在再来看cordova/builder这个模块:
1 define("cordova/builder", function(require, exports, module) { 2 var utils = require('cordova/utils');//导入工具类,由于前面模块中已经创建过,所以这里不会再次创建,而是直接返回 3 4 function each(objects, func, context) { 5 } 6 7 function include(parent, objects, clobber, merge) { 8 } 9 10 function recursiveMerge(target, src) { 11 } 12 13 //返回值 14 module.exports = { 15 }; 16 17 });
有了前面的经验,我们知道,第14行的赋值实际上就是整个构造函数的返回值,其它的则是声明了三个内部函数,用于构建返回值。
1、展开第4行的函数each,这正是开始分析的一个循环遍历实例属性的结构:
function each(objects, func, context) { for (var prop in objects) { if (objects.hasOwnProperty(prop)) {//循环遍历objects实例的属性 func.apply(context, [objects[prop], prop]);//在context环境中调用func处理属性,参数是属性值和属性名
} } }
2、再展开第10行的函数recursiveMerge(递归合并),这里也是一个循环遍历实例属性的结构,只是遍历处理复杂一些:
//递归合并对象属性(会覆盖目标对象中原有属性) function recursiveMerge(target, src) { for (var prop in src) { if (src.hasOwnProperty(prop)) {//循环遍历源对象src的实例属性 if (typeof target.prototype !== 'undefined' && target.prototype.constructor === target) { // 如果目标对象是一个构造函数,将属性赋值到其原型对象上,使得所有通过target构建的实例都可以共享src的实例属性 // 这里实际上有一个隐患,如果src有一个引用类型的实例属性ref,然后通过target创建了两个实例A、B,修改A的ref会也就修改了B的ref target.prototype[prop] = src[prop]; } else { // 简单类型直接赋值,object类型递归赋值 target[prop] = typeof src[prop] === 'object' ? recursiveMerge(target[prop], src[prop]) : src[prop]; } } } return target; }
3、再来看第7行的函数include:
function include(parent, objects, clobber, merge) { each(objects, function (obj, key) { }); }
在这里,调用了开始声明的each函数,传入对象objects和处理函数fn,但是并没有传入context,也就是说,在each内部使用apply调用函数处理属性时,是在全局环境下执行的,从each函数的源码中,我们知道,这里的处理函数fn的两个参数是属性值和属性名。具体展开fn的代码看看:
function (obj, key) { try { var result = obj.path ? require(obj.path) : {};//根据属性值中的路径,导入相应模块 if (clobber) { if (typeof parent[key] === 'undefined') { parent[key] = result;//如果目标对象不存在该属性,直接赋值为导入的模块 } else if (typeof obj.path !== 'undefined') { //目标对象已有该属性,并且已导入属性值对应模块 if (merge) { recursiveMerge(parent[key], result);//要求合并,以新导入模块为准进行合并 } else { parent[key] = result; } } result = parent[key];//将已经处理好的属性反写至中间对象,当目标对象有子对象时,再递归处理 } else { if (typeof parent[key] == 'undefined') {//如果目标对象不存在该属性,直接赋值为导入的模块 parent[key] = result; } else if (merge && typeof obj.path !== 'undefined') { // 目标对象已有该属性,并且要求合并,则以原属性为准进行合并,并将合并后的属性 recursiveMerge(result, parent[key]); parent[key] = result; } else {//目标对象已有该属性,使用该属性作为下一次递归处理的参数 result = parent[key]; } } if (obj.children) {//递归处理 include(result, obj.children, clobber, merge); } } catch(e) { utils.alert('Exception building cordova JS globals: ' + e + ' for key "' + key + '"'); } }
(1)最外层的try{}catch(e){}结构是捕获异常,并根据utils中的alert警告或写日志。
(2)根据代码中的注释,可以反过来推出include的参数parent就是需要处理属性的目标对象、objects就是需要处理的属性集、clobber则是表示以哪个为基准,为true时以objects中属性为准,false时以原对象parent中属性(如果parent未定义该属性,则仍以objects为准)为准、merge则表示是否递归合并属性及其子属性,为true时递归合并,为false时则只是简单赋值当前属性,不递归。
(3)对于objects中的属性,递归处理。
4、分析完了三个内部函数,再来看第14行的返回值:
//返回值 module.exports = { build: function (objects) { return { intoButDontClobber: function (target) {//以target中原属性为准,并且不递归合并子属性 include(target, objects, false, false); }, intoAndClobber: function(target) {//以objects中属性为准,但不递归合并子属性 include(target, objects, true, false); }, intoAndMerge: function(target) {//以objects属性为准并且递归合并子属性 include(target, objects, true, true); } }; } };
返回值是一个对象字面量,只有一个属性build,而这个build是一个函数,接受一个属性修饰参数,返回一个包含了三种不同修饰方式函数的对象。源码中函数内嵌比较多,不易理解,直接返回include好理解一点(当然,调用方式也需要相应修改):
module.exports = {build: include}
调用方式做如下修改:
var builder = require('cordova/builder'), base = require('cordova/common'); builder.build(base.objects).intoButDontClobber(window); // 修改为 builder.build(window, base.objects, false, false);