在公司接触到的一些项目中,用到了underscore.js,但一直没时间去研究underscore.js的源码。临近过年,闲散时间多了一些,便看了underscore.js的源码,顺便把自己的一些理解记录下来。
1、_采用强制new模式,自调用构造函数
我们先来看一个例子:
function Person()
{
this.name = "wen";
}
Person.prototype.age = 25;
var p1 = new Person(); //object
var p2 = Person(); //undefined
console.log(p1.age); //25
console.log(window.name); //wen
p2在调用构造函数时没有使用new操作符,会产生不同的结果,并且name会被添加到全局变量中,同时p2也没有原型链中的属性。(注:ES5严格模式下this并不会指向全局对象)。
我们看一下underscore中的处理,首先检测obj是否是_的实例,如果是直接返回obj,否则继续检查this对象是否是_的实例,如果不是,用new操作符实例_并返回。
代码如下:
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
这样做的好处就是能够保证我们调用的_对象不会丢失掉原型的链接,并且在没有使用new操作符时也能正常工作。
2、obj.length === +obj.length
在underscore中,多次用到了obj.length === +obj.length。首先我们来看一下ES5中对一元操作符+的描述。
这段话大概意思如下:
-
计算一元操作符后边的表达式,得到一个值;
-
将1中得到的值进行数值转换,并返回。
也就是+obj.length返回一个数值,obj.length === +obj.length这个表达式的意是就是说检查obj.length是不是数值,并且不为NaN,它可以改写成typeof obj.length === 'number' && (!isNaN(obj.length))。String、Function、Array类型的对象都具有length属性,而Object类型的对象,如果没有length属性,obj.length就会返回undefined。
在这里,说到一元操作符+,顺便提一下bootstrap.js中插件的+号。
+function ($) {
//the code
}(jQuery);
根据ES5中的描述,必定会执行+号后面的匿名函数。其效果跟如下代码一致:
(function($){
//the Code
}(jQuery));
3、为啥void 0
在underscore中,多处使用void 0判断对象是否存在。在ES5中,void操作符描述如下:
这段话的意思大概如下:
- 执行UnaryExpression,得到结果expr;
- 调用GetValue(expr);(GetValue(V):获取V的值);
- 返回undefined。
注:GetValue必须被调用,即使它的值是无用的,但是可能会有可见其他的效果。
也就是说,void 0 === undefined。在underscore中,之所以要用void 0,主要是因为ES3中(比如IE9以下),undefined可以被覆写。
在html中,我们也经常采用javascript:void(0)表示一个无效的链接。
4、优化isFunction
在underscore中,作者对isFunction进行了优化。我们先来看一下underscore中的代码
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
_['is' + name] = function(obj) {
return toString.call(obj) === '[object ' + name + ']';
};
});
if (typeof /./ !== 'function') {
_.isFunction = function(obj) {
return typeof obj == 'function' || false;
};
}
使用Object.prototype.toString.call(obj)来定义isFunction,紧接着,判断/./类型是否为function,如果不是则覆写isFunction,采用typeof来判断对象对象是否为function。
在这里,为啥需要添加typeof /./ !== 'function'呢?
/./是一个正则表达式,在chrome9以上浏览器中返回类型为object,而在chrome9及以下浏览器中会返回function,这个判断主要是为了兼容低版本的chrome浏览器。
在这里顺便对 || 操作符和 && 操作符做一下说明。
我们看一下ES5中 || 操作符的描述:
- 执行操作符左边表达式,得到结果lref;
- 调用GetValue(lref),得到lref的值为lval;
- 将lval转化为布尔值,若为真,则返回lval,否则继续执行;
- 执行操作符右边表达式,得到结果rref;
- 调用GetValue(rref),并将得到的值返回。
再来看一下&&操作符的描述
- 执行操作符左边表达式,得到结果lref;
- 调用GetValue(lref),得到lref的值为lval;
- 将lval转化为布尔值,若为假,则返回lval,否则继续执行;
- 执行操作符右边表达式,得到结果rref;
- 调用GetValue(rref),并将值返回。
5、+0 与 -0
Javascript中,+0 === -0 会返回true。但在实际应用中,+0和-0还是有一些区别,0前面的符号可能会影响到计算结果, 1 / 0 = Infinity, 1 / -0 = -Infinity。在underscore中,为了能够区别+0和-0,做了如下判断:
if (a === b) return a !== 0 || 1 / a === 1 / b;
检测a是否严格等于b,若是,检测a是否严格不等于0,如果为true,直接返回true,否则继续判断1 / a 是否严格等于1 / b,若是返回true,否则返回false。这样子,便能够区别出+0和-0。