闭包
整个函数在一个闭包中,避免污染全局变量。通过传入这个(其实就是窗口对象)来改变函数的作用域。和的jquery的自执行函数其实是异曲同工之妙。这种传入全局变量的方式一方面有利于代码阅读,另一方面方便压缩。
下划线写法:

(function(){
    ...
}.call(this));
jQuery的写法:

(function(window, undefined) {
    ...
})(window);

原型赋值

18 var ArrayProto = Array.prototype, ObjProto =  .prototype, FuncProto = Function.prototype;

数组,对象,函数这些本质都是函数,获取函数原型属性的原型也是为了便于压缩。简单解释一下,如果代码中要扩展属性,可能这样写

 .prototype.xxx = ...

而这种代码是不可压缩的, ,prototype这些名字改了浏览器就不认得了。

但是的上面代码中创建³³了ObjProto之后,源生代码经过压缩之后,ObjProto就可能命名成一个变量,那么原来的代码就压缩成

a.xxx = ...

一个小建议就是凡事一段代码被使用两次以上都建议定义变量(函数),有利于修改和压缩代码。

格式

29 var
nativeIsArray      = Array.isArray,
nativeKeys         =  .keys,
nativeBind         = FuncProto.bind,
nativeCreate       =  .create;

这种定义的方式省略了多余的变种,格式也美观,让我想到了崇高中的一个插件对齐。

数据判断

1194 _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  };

判断是否为DOM,DOM的节点类型属性值为1.用这里!!强转为布尔值

1200 _.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) === \'[  Array]\';
  };

判断是否为数组。由于Array.isArray函数是ECMA 5新增函数,所以为了兼容之前的版本,在原生判断函数不存在的情况下,后面重写了一个判断函数。用call函数来改变作用域可以避免当OBJ没有的toString函数报错的情况。

1205 _.is  = function(obj) {
    var type = typeof obj;
    return type === \'function\' || type === \' \' && !!obj;
};

判断是否为对象。先用typeof判断数据类型。函数也属于对象,但是由于typeof null也是对象,所以用!! obj来区分这种情况。

1219 if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
  return _.has(obj, \'callee\');
};
}

判断是否为自变量,很简单,参数有个特有属性被调用。

1239 _.isNaN = function(obj) {
return _.isNumber(obj) && obj !== +obj;
  };

NaN这个值有两个特点:1。它是一个数; 2.不等于它自己。
‘+‘放在变量前面一般作用是把后面的变量变成一个数,在这里已经判断为一个数仍加上’+’,为了的英文把var num = new Number()这种没有值的数字也。归为NaN的。

1244   _.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) === \'[  Boolean]\';
};

是不是以为如果是布尔值不是真就是假的呢?还有第3中情况var b = new Boolean().B也是布尔值。

1254   _.isUndefined = function(obj) {
return obj === void 0;
};

用虚0来表示undefined,非常有意思的小技巧。不过常用方式还是if(xxx)来判断是不是未定义。

eq是下划线的一个内置函数,代码太长,不粘贴了.isEmpty调用了这个函数。整个思路由易到难,先用===比较简单数据,然后用的toString来判断是否相等,最后用递归处理复杂的阵列,功能和对象对象。

1091 if (a === b) return a !== 0 || 1 / a === 1 / b;

这里为了区分 ‘0’ 和 ‘-0’,因为这两个数对计算结果是有影响的。

1098 var className = toString.call(a);
if (className !== toString.call(b)) return false;
switch (className) {
  // Strings, numbers, regular  s, dates, and booleans are compared by value.
  case \'[  RegExp]\':
  // RegExps are coerced to strings for comparison (Note: \'\' + /a/i === \'/a/i\')
  case \'[  String]\':
    // Primitives and their corresponding   wrappers are equivalent; thus, `\"5\"` is
    // equivalent to `new String(\"5\")`.
    return \'\' + a === \'\' + b;
  case \'[  Number]\':
    // `NaN`s are equivalent, but non-reflexive.
    //  (NaN) is equivalent to NaN
    if (+a !== +a) return +b !== +b;
    // An `egal` comparison is performed for other numeric values.
    return +a === 0 ? 1 / +a === 1 / b : +a === +b;
  case \'[  Date]\':
  case \'[  Boolean]\':
    // Coerce dates and booleans to numeric primitive values. Dates are compared by their
    // millisecond representations. Note that invalid dates with millisecond representations
    // of `NaN` are not equivalent.
    return +a === +b;
}

这里是对简单对象进行判断,分为两类,类一的英文String和RegExp,数据这种直接toString然后判断。另一类是Number,Date和Boolean,通过转换成数字判断。

1150 aStack.push(a);
bStack.push(b);
if (areArrays) {
  length = a.length;
  if (length !== b.length) return false;
  while (length--) {
    if (!eq(a[length], b[length], aStack, bStack)) return false;
  }
} else {
  var keys = _.keys(a), key;
  length = keys.length;
  if (_.keys(b).length !== length) return false;
  while (length--) {
    key = keys[length];
    if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
  }
}
aStack.pop();
bStack.pop();

对于数组和对象只能用递归了,同时用aStack和BSTACK来暂存递归中的子对象。这里一个小技巧的就是先判断数组/属性的长度,如果不相等可以有效地减少递归。

转自:https://yalishizhude.github.io/2015/09/22/underscore-source/

收藏 打印