前文我们说过函数可以生成函数作用域,并且各个函数的作用域并不能重叠,除非一个函数被另一个函数完全包裹,那么它的作用域也被另一个函数完全覆盖。但是,究竟是什么生成了作用域?JavaScript 中的其他结构能生成作用域吗?
函数中的作用域
函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。这种设计方案是非常有用的,能充分利用 JavaScript 变量可以根据需要改变值类型的“动态”特性。
隐藏内部实现
隐藏内部实现指的是不将所有的函数与变量都设计成全局可访问,而是根据功能和特性,设计上将具体内容私有化了。
隐藏作用域中的变量和函数,可以避免同名标识符之间的冲突,两个标识符可能具有相同的名字但用途却不一样,无意间可能会造成命名冲突。冲突会导致变量的值被意外覆盖。
实现隐藏内部实现的方法有两种:
- 全局命名空间
当程序中加载了多个第三方库时,如果它们没有将妥善地将内部私有的函数或变量隐藏起来,就会很容易引发冲突。为了解决这个问题,第三方库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象,这个对象被用作库的命名空间,所有需要暴露给外界的功能都会成为这个命名空间的属性,而不是将自己的标识符暴露在顶级的词法作用域中。
- 模块管理
使用模块管理器,任何库都无需将标识符加入到全局作用域中,而是通过依赖管理器的机制将库的标识符显式地导入到另外一个特定的作用域中。
隐藏函数变量
通过函数作用域来隐藏变量会带来另一个问题,那就是一个无意义的函数变量会被投放到全局作用域中。我们也可以通过以下两种方式来解决这个问题:
- 匿名函数
匿名函数是通过函数表达式来创建函数,与函数声明不同的是,匿名函数无需设置名称标识符。
// 通过 function 实现匿名表达式
setTimeout(function() { console.log("I waited 1 second! "); }, 1000 );
// 通过 ES6 中的箭头函数 实现匿名表达式
setTimeout(() => { console.log("I waited 1 second! "); }, 1000 );
匿名函数最大的缺点就是,无法很方便的调用自身。
- 立即执行表达式IIFE(Immediately Invoked Function Expression)
var a = 2;
(function IIFE(global) {
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})(window);
console.log(a); // 2
块作用域
块作用域是一个用来对之前的最小授权原则进行扩展的工具,将代码从在函数中隐藏信息扩展为在块中隐藏信息。
for (var i=0; i<10; i++) { console.log(i); }
try { undefined(); // 执行一个非法操作来强制制造一个异常 }
catch (err) { console.log(err); // 能够正常执行! }
console.log(err); // ReferenceError: err not found
正如你所看到的,err仅存在catch分句内部,当试图从别处引用它时会抛出错误。
但是当你在 for 块作用域中创建了 i 变量,却发现全局作用域都能使用 i。幸好,ES6 改变了现状,引入了新的 let 关键字和 const 关键字,提供了除 var 以外的变量声明方式。