5.4.4. 作用域与闭包
5.4.4.1. 作用域与作用域链
5.4.4.1.1. 作用域
简单来说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。JavaScript的作用域是靠函数来形成的,也就是说一个函数的变量在函数外不可以访问。
作用域可以分为全局作用域、局部作用域和块级作用域,其中全局作用域主要有以下三种情况:
- 函数外面定义的变量拥有全局作用域
- 未定义直接赋值的变量自动声明为拥有全局作用域
- window对象的属性拥有全局作用
局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所以也会把这种作用域称为函数作用域。
5.4.4.1.2. 作用域泄漏
在ES5标准时,只有全局作用域和局部作用域,没有块级作用域,这样可能会造成变量泄漏的问题。例如:
- var i = 1;
- function f() {
- console.log(i)
- if (true) {
- var i = 2;
- }
- }
- f(); // undefined
5.4.4.1.3. 作用域提升(var Hoisting)
在JavaScript中,使用var在函数或全局内任何地方声明变量相当于在其内部最顶上声明它,这种行为称为Hoisting。例如下面这段代码等效于第二段代码
- function foo() {
- console.log(x); // => undefined
- var x = 1;
- console.log(x); // => 1
- }
- foo();
- function foo() {
- var x;
- console.log(x); // => undefined
- x = 1;
- console.log(x); // => 1
- }
- foo();
5.4.4.1.4. 作用域链
当函数被执行时,总是先从函数内部找寻局部变量,如果找不到相应的变量,则会向创建函数的上级作用域寻找,直到找到全局作用域为止,这个过程被称为作用域链。
5.4.4.2. 闭包
函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。
在JavaScript中,并没有原生的对private方法的支持,即一个元素/方法只能被同一个类中的其它方法所调用。而闭包则是一种可以被用于模拟私有方法的方案。另外闭包也提供了管理全局命名空间的能力,避免非核心的方法或属性污染了代码的公共接口部分。下面是一个简单的例子:
- var Counter = (function() {
- var privateCounter = 0;
- function changeBy(val) {
- privateCounter += val;
- }
- return {
- increment: function() {
- changeBy(1);
- },
- decrement: function() {
- changeBy(-1);
- },
- value: function() {
- return privateCounter;
- }
- }
- })();
- console.log(Counter.value()); /* logs 0 */
- Counter.increment();
- Counter.increment();
- console.log(Counter.value()); /* logs 2 */
- Counter.decrement();
- console.log(Counter.value()); /* logs 1 */
5.4.4.3. 全局对象
全局对象是一个特殊的对象,它的作用域是全局的。
全平台可用的全局对象是 globalThis
,它跟全局作用域里的this值相同。另外在浏览器中存在 self
和 window
全局对象,Web Workers中存在 self
全局对象,Node.js 中存在 global
全局对象。