这篇文章是从另一个角度来理解作用域的使用。作用域正常是在语法在声明的变量或函数的位置决定的。反过来想,我们可以把语法用作用域来包裹。这样做的出于软件设计中最小特权原则(最小化的授权 和 最小化的暴露),来看下代码:

function doSomeThing(a){
   b = a + doSomeThingElse(a * 2);
   console.log(b * 3);
}
function doSomeThingElse(a){
   return a - 1;
}
var b;
doSomeThing(2); //15

是不是觉得不合理,按照最小特权原则,应该改为:

function doSomeThing(a){
   function doSomeThingElse(a){
      return a - 1;
   }
   var b;
   b = a + doSomeThingElse(a * 2);
   console.log(b * 3)
}
doSomeThing(2); //15

这样的好处是,保证b和doSomeTingElse被doSomeThing控制,保护了内部的私有化,避免被外部影响。

另一个好处是避免同名标示符冲突。

function foo(){
   function bar(a){
      i = 3;//被调用时,修改了循环变量中的i
      console.log(a + i);
   }
   for(var i = 0; i < 10; i++){
      bar(i * 2);//无限循环中……
   }
}
foo();

按照最小特权原则,需要修改bar中var i = 3;保证i所属bar的作用域。或者将bar中的i命名改为j = 3。有时在开发中我们需要使用同名命名,所以保证作用域的私有化才是最佳实践。

在全局命名空间中容易出现命名冲突,比如引用多个第三发库。通常外部库会在全局命名空间中声明一个比较特殊的标示的变量(一般是个对象),将所有方法都变成这个标示的属性,不把它们放倒全局作用域中,如:

var myReallyCoolLibrary = {
   awesome : 'stuff',
   doSomeThing :  function(){

   },
   doAnotherThing :  function(){
      
   }
};

作用域私有虽然能保护内部的标示,但是还有些问题,1、必须通过声明一个具名函数;2、函数名对全局作用域会造成污染;3、必须调用函数名才能执行代码;所以更好的选择是自执行匿名函数,(function(){})(),没有函数声明不会污染全局 作用域,而且自动执行;

var a = 2;
(function(){
   var a = 3;
   console.log(a);
})();
console.log(a);

匿名函数存在一些问题:

  1. 匿名函数没有名称,在栈中调用比较困难;
  2. 当递归调用时,只能依靠过期的arguments.callee调用;
  3. 没有名称,使得阅读代码变得困难;

所以始终给函数表达式一个命名是最佳实践,如:

setTimeout(function setTimeOutHandler(){
   console.log('……')
}, 1000);

再说下立即执行匿名函数IIFE

(function(){…})()

还有人习惯这样使用

((function(){…})())

这两种反式只是使用习惯不同,实质是一样的。

在自动执行匿名函数的第二个()中,可以当作函数调用进行传参,如:

var a = 2;
(function IIFE(global){
   var a = 3;
   console.log(a); // 3
   console.log(global.a); //2
})(window);

将全局变量传参到函数中,使用global弥补没有“全局”名称的缺失,改进代码风格;

这种方式还有另外一个应用场景,保护 undefined 值被修改;

undefined = true; //给其他大码挖了个大坑,据对不要这样使用
(function IIFE(undefined){
   var a;
   if(a === undefined){
      console.log('受到保护');
   }
})();

IIFE还有另外一种倒置使用方式,在UMD模块写法中比较常用,这种代码略显沉长,但有时易于理解;

var a = 2;
(function(def){
   def(window);
})(function def(global){
   var a = 3;
   console.log(a);
   console.log(global.a);
});