函数中的作用域
1.无论标识符声明出现在作用域中的何处, 这个标识符所代表的变量或函数都将附属于所处作用域的气泡。
2.这些标识符全都无法从全局作用域中进行访问, 因此会导致ReferenceError 错误。
隐藏内部实现
1.从所写的代码中挑选出一个任意的片段, 然后用函数声明对它进行包装,实际上就是把这些代码“隐藏” 起来了。
function doSomething(a) { b = a + doSomethisEles(a * 2); console.log(b * 3); } function doSomethiingElse(a) { return a - 1; } var b; doSomethis(2); // 15
function doSomethis(a) { function doSomethisElse(a) { return a - 1; } var b; b = a + doSomethingElse(a * 2); console.log(b * 3); } doSomethis(2); // 15
2.为什么“隐藏” 变量和函数是一个有用的技术?
它们大都是从最小特权原则中引申出来的, 也叫最小授权或最小暴露原则。 这个原则是指在软件设计中, 应该最小限度地暴露必要内容, 而将其他内容都“隐藏” 起来, 比如某个模块或对象的 API 设计。
规避冲突
function foo() { function bar(a) { i = 3; // 修改 for 循环所属作用域中的 i console.log( a + i ); } for (var i=0; i<10; i++) { bar( i * 2 ); // 糟糕, 无限循环了! } } foo();
1.“隐藏” 作用域中的变量和函数所带来的另一个好处, 是可以避免同名标识符之间的冲突,两个标识符可能具有相同的名字但用途却不一样, 无意间可能造成命名冲突。
冲突会导致变量的值被意外覆盖。
2.函数内部的赋值操作需要声明一个本地变量来使用, 采用任何名字都可以 。
但是软件设计在某种情况下可能自然而然地要求使用同样的标识符名称, 因此在这种情况下使用作用域来“隐藏” 内部声明是唯一的最佳选择。
全局命名空间
1.库通常会在全局作用域中声明一个名字足够独特的变量, 通常是一个对象。
这个对象被用作库的命名空间, 所有需要暴露给外界的功能都会成为这个对象(命名空间) 的属性,而不是将自己的标识符暴漏在顶级的词法作用域中。
函数作用域
1.首先,必须声明一个具名函数 foo(), 意味着 foo 这个名称本身“污染” 了所在作用域(在这个例子中是全局作用域)。
其次, 必须显式地通过函数名(foo()) 调用这个函数才能运行其中的代码。
2.如果函数不需要函数名(或者至少函数名可以不污染所在作用域), 并且能够自动运行,这将会更加理想。
3.如果 function 是声明中的第一个词, 那么就是一个函数声明, 否则就是一个函数表达式。
(function foo() { var a = 3; console.log(a); // 3 })(); console.log(a); // 2
4.foo 被绑定在函数表达式自身的函数中而不是所在作用域中。
立即执行函数表达式
1.由于函数被包含在一对 ( ) 括号内部, 因此成为了一个表达式, 通过在末尾加上另外一个( ) 可以立即执行这个函数, 比如 (function foo(){ .. })()。
第一个 ( ) 将函数变成表达式, 第二个 ( ) 执行了这个函数。
2.相较于传统的 IIFE 形式, 很多人都更喜欢另一个改进的形式: (function(){ .. }())。
var a = 2; (function IIFE(global) { var a = 3; console.log(a); // 3 console.log(global.a); // 2 })(window); console.log(a); // 2
3.IIFE 的另一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去。
try/catch
try { undefined(); // 执行一个非法操作来强制制造一个异常 } catch (err) { console.log( err ); // 能够正常执行! } console.log( err ); // ReferenceError: err not found
1.非常少有人会注意到 JavaScript 的 ES3 规范中规定 try/catch 的 catch 分句会创建一个块作用域, 其中声明的变量仅在 catch 内部有效。