angular源码分析:angular中各种常用函数,比较省代码的各种小技巧

angular的工具函数

在angular的API文档中,在最前面就是讲的就是angular的工具函数,下面列出来

angular.bind           //用户将函数和对象绑定在一起,返回一个新的函数
angular.bootstrap      //angular启动函数,一般不用,除了e2e测试

angular.copy           //对象拷贝
angular.element        //jQlite,作用类似于jQuery
angular.equals         //对象比较。用 == 对两个对象进行比较,除非他们是同一个对象的引用,否则都不会相等。
angular.extend         //对象扩展,将两个对象的属性求并集
angular.forEach        //功能类似于jQuery中each函数,

angular.noop           //额,一个空函数
angular.identity       //额,这个函数可以返回它的第一个函数

angular.injector       //用来创建一个注入器,可以用来给其他函数注入所依赖的对象
angular.module         //两个参数,注册模块;一个参数,返回模块。太常用了。

angular.fromJson       //将json转成对象
angular.toJson         //将对象转成json


angular.uppercase   //小写转大写
angular.lowercase   //大写转小写

//类型检查
angular.isArray
angular.isDate
angular.isDefined
angular.isElement
angular.isFunction
angular.isNumber
angular.isObject
angular.isString
angular.isUndefined

他们都是怎么实现的,并且怎么用

1.类型检查函数

function isUndefined(value) {return typeof value === 'undefined';}

function isDefined(value) {return typeof value !== 'undefined';}

function isObject(value) {
  return value !== null && typeof value === 'object';  //需要判断null的情况,在js中null是一个object,但是又不是我们理解的object
}

function isString(value) {return typeof value === 'string';}

function isNumber(value) {return typeof value === 'number';}

function isDate(value) {
  return toString.call(value) === '[object Date]';  //前面有定义:toString = Object.prototype.toString,  //技巧1
}

var isArray = Array.isArray;

function isFunction(value) {return typeof value === 'function';}


function isElement(node) {
  return !!(node &&      //技巧2
    (node.nodeName  // we are a direct element
    || (node.prop && node.attr && node.find)));  // we have an on and find method part of jQuery API
}

技巧1:通过短引用的定义来减少代码书写

技巧2:使用双叹号(!!),将表达式的值限制在true/false间。作用类似于C语言中的 bool(expression),将表达式的值强制转换为布尔值。

2.下面分析大小写转换


//方案1:大小写都是利用字符串对象本身的大小写转换函数来完成的

var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};

//方案2:自定义大小写转换函数
var manualLowercase = function(s) {
  /* jshint bitwise: false */
  return isString(s)
      ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) //技巧3
      : s;
};
var manualUppercase = function(s) {
  /* jshint bitwise: false */
  return isString(s)
      ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
      : s;
};


// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
// with correct but slower alternatives.
//土耳其环境,浏览器的默认提供大小写转换函数会出问题,所以采用方案2
if ('i' !== 'I'.toLowerCase()) {
  lowercase = manualLowercase;
  uppercase = manualUppercase;
}

技巧3:字符串对象的replace函数妙用

stringObject.replace(regexp/substr,replacement)

三种使用境界:

1.两个参数都是字符或者字符串。函数会在stringObject中查找第一个字符或字符串,并且替换成第二个字符或字符串

2.第一参数使用正则表达式,第二参数使用字符或字符串。函数会用正则表达式的方式进行查找并替换

3.第一参数是字符串或正则表达式,第二个参数是一个回掉函数。replace函数会按第一个参数查找,然后将值传到第二参数指定的函数中得到反馈值替换被查找到的字符串。

例子:写一个函数处理下面字符串,给下面代码中的a标签中的内容都添加一个括号

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <a name="dapeng">我是大鹏</a>
    <a href="#dapeng">点击,链接到大鹏</a>
</body>
</html>

js函数:

function add_brackets(inputStr){
    return typeof inputStr == 'string' && inputStr.replace(/(<a.+?>)(.+)(<\/a>)/im,function(){
        return arguments[1] + '(' + arguments[2] + ')' + arguments[3];
    });
}

3.关于angular.noop和angular.identity

下面是angular注释中给出的angular.noop和angular.identity的使用例子。

     function foo(callback) {
       var result = calculateResult();
       (callback || angular.noop)(result); //技巧4:不用再去判断callback是否定义
     }

     function transformer(transformationFn, value) {
       return (transformationFn || angular.identity)(value);
     };

技巧4:定义一些默认的对象,在函数中,将参数与默认对象求或,可以省去参数是否定义的判断。

拷贝,angular.copy

var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
function isTypedArray(value) {
  return TYPED_ARRAY_REGEXP.test(toString.call(value));
}

/**
 * Set or clear the hashkey for an object.
 * @param obj object
 * @param h the hashkey (!truthy to delete the hashkey)
 */
function setHashKey(obj, h) {
  if (h) {
    obj.$$hashKey = h;
  } else {
    delete obj.$$hashKey;
  }
}

function copy(source, destination, stackSource, stackDest) {  //技巧5
  if (isWindow(source) || isScope(source)) {
    throw ngMinErr('cpws',
      "Can't copy! Making copies of Window or Scope instances is not supported.");
  }
  if (isTypedArray(destination)) {
    throw ngMinErr('cpta',
      "Can't copy! TypedArray destination cannot be mutated.");
  }

  if (!destination) {
    destination = source;
    if (isObject(source)) {
      var index;
      if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
        return stackDest[index];
      }

      // TypedArray, Date and RegExp have specific copy functionality and must be
      // pushed onto the stack before returning.
      // Array and other objects create the base object and recurse to copy child
      // objects. The array/object will be pushed onto the stack when recursed.
      if (isArray(source)) {
        return copy(source, [], stackSource, stackDest);
      } else if (isTypedArray(source)) {
        destination = new source.constructor(source);
      } else if (isDate(source)) {
        destination = new Date(source.getTime());
      } else if (isRegExp(source)) {
        destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
        destination.lastIndex = source.lastIndex;
      } else if (isFunction(source.cloneNode)) {
          destination = source.cloneNode(true);
      } else {
        var emptyObject = Object.create(getPrototypeOf(source)); //getPrototypeOf = Object.getPrototypeOf, 这里用原型对象创建了一个空对象
        return copy(source, emptyObject, stackSource, stackDest); //将其转化为destination有定义的调用
      }

      if (stackDest) {
        stackSource.push(source);
        stackDest.push(destination);
      }
    }
  } else {
    if (source === destination) throw ngMinErr('cpi',
      "Can't copy! Source and destination are identical.");

    stackSource = stackSource || [];
    stackDest = stackDest || [];

    if (isObject(source)) {
      stackSource.push(source);
      stackDest.push(destination);
    }

    var result, key;
    if (isArray(source)) {  //如果源对象是一个数组,循环拷贝数组
      destination.length = 0;
      for (var i = 0; i < source.length; i++) {
        destination.push(copy(source[i], null, stackSource, stackDest));
      }
    } else {
      var h = destination.$$hashKey;
      if (isArray(destination)) {
        destination.length = 0;
      } else {
        forEach(destination, function(value, key) {
          delete destination[key];
        });
      }
      if (isBlankObject(source)) {
        // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
        for (key in source) {
          destination[key] = copy(source[key], null, stackSource, stackDest);
        }
      } else if (source && typeof source.hasOwnProperty === 'function') {  //挨个拷贝键值,形成递归拷贝
        // Slow path, which must rely on hasOwnProperty
        // 慢路径,必须依赖于对象拥有hasOwnProperty方法
        for (key in source) {
          if (source.hasOwnProperty(key)) {
            destination[key] = copy(source[key], null, stackSource, stackDest);
          }
        }
      } else {
        // Slowest path --- hasOwnProperty can't be called as a method 
        // 更慢的路径,hasOwnProperty不能被当成对象的方法调用的时候
        for (key in source) {
          if (hasOwnProperty.call(source, key)) { //var hasOwnProperty = Object.prototype.hasOwnProperty;
            destination[key] = copy(source[key], null, stackSource, stackDest);
          }
        }
      }
      setHashKey(destination,h);  
    }
  }
  return destination;
}

技巧5:构造递归函数。用递归的方式处理问题的条件在于,能把问题转化为规模缩小了的同类问题的子问题。缺点,效率较低。理论上任何递归问题,都可以用循环来完成。

对象是否相等,angular.equals

function equals(o1, o2) {
  if (o1 === o2) return true;
  if (o1 === null || o2 === null) return false;
  if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
  var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
  if (t1 == t2) {
    if (t1 == 'object') {
      if (isArray(o1)) {  //数组,等每个键值递归调用
        if (!isArray(o2)) return false;
        if ((length = o1.length) == o2.length) {
          for (key = 0; key < length; key++) {
            if (!equals(o1[key], o2[key])) return false;
          }
          return true;
        }
      } else if (isDate(o1)) { //Date,转成时间戳比较
        if (!isDate(o2)) return false;
        return equals(o1.getTime(), o2.getTime());
      } else if (isRegExp(o1)) {  //正则式,转成字符串比较
        return isRegExp(o2) ? o1.toString() == o2.toString() : false;
      } else {
        if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
          isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
        keySet = createMap();
        for (key in o1) {  //对象属性的比较,也是递归调用,遍历第一个对象所有的属性,发现有不等的地方,立即返回false
          if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
          if (!equals(o1[key], o2[key])) return false;
          keySet[key] = true;
        }
        for (key in o2) { //遍历第二对象,继续检查,发现两个对象有不等的地方,立即返回false
          if (!(key in keySet) &&
              key.charAt(0) !== '$' &&
              isDefined(o2[key]) &&
              !isFunction(o2[key])) return false;
        }
        return true;
      }
    }
  }
  return false;
}

上一期:angular源码分析:angular中的依赖注入式如何实现的

下一期:angular源码分析:angular的源代码目录结构

ps:也许这个该先讲,可能有同学看到一堆文件,不知从何看起。