1. 引言
underscore.js是一个1500行左右的Javascript函数式工具库,里面提供了很多实用的、耦合度极低的函数,用来方便的操作Javascript中的数组、对象和函数,它支持函数式和面向对象链式的编程风格,还提供了一个精巧的模板引擎。理解underscore.js的源码和思想,不管是新手,还是工作了一段时间的人,都会上升一个巨大的台阶。虽然我不搞前端,但是在一个星期的阅读分析过程中,仍然受益匪浅,决定把一些自认为很有意义的部分记录下来。
2. 命名空间
打开underscore.js,最先看到的就是下面这个结构:
(function() {
var root = this;
var previousUnderscore = root._;
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
root._ = _;//让外界引用当前作用域;把nodeJS省略了
_.noConflict = function() {
//将之前保存的window._还原
root._ = previousUnderscore;
return this; //将真正的 _ 返回
};
}.call(this)); //this是window
整个匿名函数一定义就立刻执行,并且把此函数内部的执行上下文this指向window.
root._ = _;
是将局部变量 _ 的引用交给window,这样就算整个匿名函数执行完了,其作用域也不会释放内存空间,因为当前作用域正被外部引用,这样就形成了一个天然的隐蔽空间,_ 是唯一暴露的接口,任何想要被外界访问的属性或函数都必须绑定在 _ 这个对象上,而一些内部辅助属性或函数可以非常安全的直接定义在这个空间里,外界无法访问。大多数JS库都是这么做的,这样可以将命名空间污染降到最低。
但是,_ 还是有可能在全局作用域中出现命名冲突,比如:
<script>
var _ = 'myUnderscore';
</script>
<script src="underscore.js"></script><!-- 一导入就覆盖了 -->
<script>
console.log(_);
</script>
或者
<script src="underscore.js"></script>
<script>
var _ = 'myUnderscore';
console.log(_);
<script>
都会发生冲突,所以underscore.js做了冲突处理,underscore.js一加载就将window中的 _ 属性值保存在preserveUnderscore
这个变量中(window._
存不存在无所谓),调用_.noConflict
函数会将之前的preserveUnderscore
还给window._
,然后再把真正的 _ 返回,这时应该换一个变量名,用一个非 _ 的变量名来接收:
<script>
var _ = 'myUnderscore';
</script>
<script src="underscore.js"></script>
<script>
var __ = _.noConflict();
console.log(_);//myUnderscore
console.log(__);//从此__就是underscore,以后都用这个变量名调用underscore.js
同样这种解决命名冲突的方法在别的库中也有用到。
3. 构造函数
var _ = function(obj) {
if (obj instanceof _) return obj;
//如果用new关键字调用,则this指向新创建的实例;否则this指向window
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
在上面的代码片段中,可以看到 new _(obj)
,说明 _ 本身就是个构造函数,underscore.js的所有对外函数都绑定在 _ 这个构造函数身上,使用时直接_.functionName()
来调用,同时还允许创建 _ 的实例,并且允许不用new关键字进行实例的创建,它会自行修正。它的实例包含一个属性_wrapped
,目的是允许链式风格的调用。
4. 总结
- 闭包在命名空间上的应用。
- 构造函数对非new调用方式的修正。