• PhoneGap源码分析10——cordova/builder


      这一篇我们分析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);
    拣尽寒枝不肯栖,寂寞沙洲冷。
    郴江幸自绕郴山,为谁流下潇湘去?
    欲将心事付瑶琴,知音少,弦断有谁听?
    倩何人,唤取红巾翠袖,揾英雄泪!
    零落成泥碾作尘,只有香如故!
  • 相关阅读:
    FastDFS安装配置过程中出现错误提示"/home/yuqing/fastdfs" can't be accessed, error info: No such file or directory
    dubbo-monitor安装监控中心,管理控制台安装网页一直访问不到,解决bug的方式记录
    dubbo-monitor安装监控中心,管理控制台安装
    zookeeper伪分布式集群安装
    zookeeper单节点安装
    JedisCluster操作redis集群demo
    Redis Cluster集群的搭建
    redis3.0.6安装配置
    Windows注册表中修改CMD默认路径
    eclipse中使用mybatis-generator逆向代码生成工具问题解决记录
  • 原文地址:https://www.cnblogs.com/linjisong/p/2639666.html
Copyright © 2020-2023  润新知