JS进阶之路之——作用域(二)

最小授权原则

最小授权原则是指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来,比如某个模块或对象的 API 设计。

这个原则可以延伸到如何选择作用域来包含变量和函数。如果所有变量和函数都在全局作 用域中,当然可以在所有的内部嵌套作用域中访问到它们。但这样会破坏前面提到的最小 特权原则,因为可能会暴漏过多的变量或函数,而这些变量或函数本应该是私有的,正确 的代码应该是可以阻止对这些变量或函数进行访问的。

例如:


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

在这个代码片段中,变量 b 和函数 doSomethingElse(..) 应该是 doSomething(..) 内部具体 实现的“私有”内容。给予外部作用域对 b 和 doSomethingElse(..) 的“访问权限”不仅 没有必要,而且可能是“危险”的,因为它们可能被有意或无意地以非预期的方式使用, 从而导致超出了 doSomething(..) 的适用条件。更“合理”的设计会将这些私有的具体内容隐藏在 doSomething(..) 内部,

例如:


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

现在,b 和 doSomethingElse(..) 都无法从外部被访问,而只能被 doSomething(..) 所控制。 功能性和最终效果都没有受影响,但是设计上将具体内容私有化了,设计良好的软件都会 依此进行实现。

规避冲突

当我们的程序代码逐渐多起来,难免会出现变量冲突。那么如何规避冲突就显得额外重要。

函数可以把标识符严谨的"隐藏"起来,外部无法访问到,利用这个特性我们可以很好的规避冲突。


function foo() {    
    var a = 1;
}
function bar() {    
    var a = 2;
}

foo和bar中定义了相同的变量a,但是却不会相互造成影响。因为函数可以很好的把标识符"隐藏"起来。

变量冲突的一个典型例子存在于全局作用域中。当程序中加载了多个第三方库时,如果它 们没有妥善地将内部私有的函数或变量隐藏起来,就会很容易引发冲突。 这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象 被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象(命名空间)的属 性,而不是将自己的标识符暴漏在顶级的词法作用域中。

例如:

扫描二维码关注公众号,回复: 1910694 查看本文章

var myLibrary = {    
    name: 'echo',    getName: function() {        
        console.log( this.name );    
    }
}

函数声明 VS 函数表达式

函数声明和函数表达式判别的依据是:函数的生命是否以function关键词开始。 以关键词function 开始的声明是函数声明,其余的函数声明全部是函数表达式。


//函数声明function foo() {}
//函数表达式var foo = function () {};
(function() {})();

具名函数 VS 匿名函数

  • 具名函数 拥有名字的函数


function foo() {}
var foo = function bar() {}
setTimeout( function foo() {} )+function foo() {}();

需要注意:函数声明一定要是具名函数。

  • 匿名函数 没有名字的函数


var foo = function () {}setTimeout( function foo() {} )-function foo() {}();

立即执行函数(IIFE)


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

该函数是以()开始,不是以关键词function开始,因此IIFE是函数表达式

函数名对 IIFE 当然不是必须的,IIFE 最常见的用法是使用一个匿名函数表达式。虽然使 用具名函数的 IIFE 并不常见,但它具有以下优势:

  1. 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。

  2. 如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用, 比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑 自身。

  3. 匿名函数省略了对于代码可读性/可理解性很重要的函数名。一个描述性的名称可以让 代码不言自明。

因此具名函数的 IIFE 也是一个值得推广的实践。


(function() {}())

这也是IIFE的一种表达方式,功能上和上面那种方式是一致的。选择哪种全凭个人爱好。

IIFE 也可以和其他形式的函数一样实现参数的传递(多说一句:参数传递是按值传递)。


(function foo(a) {    console.log(a);})(3);

这个模式的另外一个应用场景是解决 undefined 标识符的默认值被错误覆盖导致的异常(虽 然不常见)。将一个参数命名为 undefined,但是在对应的位置不传入任何值,这样就可以 保证在代码块中 undefined 标识符的值真的是 undefined:


undefined = true; // 给其他代码挖了一个大坑!绝对不要这样做! 
(function IIFE( undefined ) {    
    var a;    if (a === undefined) {        
        console.log( "Undefined is safe here!" );    
    }})();
  • UMD (Universal Module Definition)

IIFE 还有一种变化的用途是倒置代码的运行顺序,将需要运行的函数放在第二位,在 IIFE 执行之后当作参数传递进去。尽管这种模式略显冗长,但有些人认为它更易理解。


猜你喜欢

转载自blog.csdn.net/wwwxuewen/article/details/80872978