函数声明 - 创建作用域气泡
首先观察下面的代码:
function foo(a){
var b = 2;
function bar(){
// do something
}
}
在这个代码片段中,foo(a) 创建了一个作用域气泡,其中包含了标识符a和bar,在全局环境中是无法访问到a和bar的。但在整个函数的范围内,属于整个函数的全部变量都可以任意使用/复用。
封装技术 - 隐藏内部实现
这是为什么要设计变量作用域的一个关键点。
对函数的传统认识就是,先声明一个函数,再往里面塞代码;那么从另一个角度看,代码被提炼出来打包到一个函数里,这就是最小特权原则,或者叫最小暴露原则,在软件设计中最小限度地暴露必要内容,将其他内容隐藏起来,这是模块或者API的设计基础。
那么隐藏其内部内容就是技术关键了,由于JavaScript中常遇到类型变化,需要灵活的变量复用能力,一层层作用域内部嵌套成为了较好的设计。
function doSomething(a){
b = a + doSomethingElse(a * 2)
console.log(b * 3)
}
function doSomethingElse(a){ // danger!!!
return a - 1;
}
var b; // danger!!!
doSomething(2);
像这里,暴露出doSomethingElse和 变量b是一个糟糕的选择,它们可能会被有意或者无意地以非预期的方式使用,较好的做法是把他们放进doSomething(a) 里面去。
function doSomething(a){
function doSomethingElse(b){
return a - 1;
}
var b;
b = a + doSomethingElse(a * 2);
console.log(b);
}
避免冲突 - 第三方库作用域
对象命名空间
当程序加载了很多第三方库,如果他们没有将自己的函数或者变量隐藏起来,就容易产生冲突。
所以这些库在全局作用域中声明一个足够独特的变量,通常是一个对象,被叫做库的命名空间,所有功能都成为这个对象的属性,而不是直接暴露出来。
var MyReallyCoolLibrary = {
awesome: "stuff";
doSomething: function(){
// do something
}
}
模块管理
另一种方法和现代的模块机制很像,使用模块管理器,任何库都不需要将标识符加入到全局作用域,而是通过库的机制进行模块管理。它们只是利用作用域的规则来避免冲突。
匿名函数 - 函数表达式&函数声明
当你需要声明一个函数并进行操作,使用下面的代码:
(function foo(){
var a = 3;
console.log(a);
})();
此时foo() 函数将不会污染全局环境,而且随着代码块立即执行,当function作为开头就是函数声明否则为表达式,此时的 (function...就是一个函数表达式。
这样的匿名函数可能有需要考虑的缺点:
匿名函数在栈追踪中可能导致调试困难;
当函数需要引用自身时只能使用已经过期的arguments.callee引用
(可避免的) 省略掉了标识符带来的可读性/可理解性