作用域
一般认为 JS 中作用域有三种:
- 全局作用域:一个脚本运行代码的默认作用域;
- 模块作用域:一个模块运行代码的默认作用域;
- 函数作用域:一个函数运行代码的默认作用域。
而由于 let/const
声明变量的作用域,是比函数作用域更加具有块级属性的,所以称块级作用域,任何一个 {}
包围的代码都是一个块;函数是特殊的块,脚本是一个大的块,模块是多个脚本的集合,是更大的块。
函数是特殊的块?这是由于,函数体内就算是 var
声明的变量,在外部也无法使用,这和其他 {}
构成的块不同。换句话说,var
只承认函数的块级作用域。
/* let/const 声明变量的作用域 */
{ let a = 1; }
console.log(a) // 报错,块内的变量在外部失效,报未定义的错
{ let a = 1 }
{ console.log(a) } // 报错,此块内没有变量a
for (let i = 0; i < 5; i++) { }
console.log(i) // 报错,块内的变量在外部失效,报未定义的错
for (var i = 0; i < 5; i++) { }
console.log(i) // 5 var 只承认函数的块级作用域
闭包
闭包是 JS 中最强大的特性之一。尽管功能强大,但除了完成特定任务时,才会函数嵌套。
概念
JS 允许函数嵌套,且根据 块/函数
的作用域特性(内部可以访问外部声明定义的变量),将外部函数声明的变量等组成的环境和一个内部函数组合封装起来,就构成一个闭包。
在下面一个简单的计数器例子中,内部函数 counter
和变量 n/init
组成的环境,构成闭包。
注意:返回的函数不能是 new Function
构造出来的(Function
的传参模式是字符串,指向全局环境,无法访问外部函数环境)。
function MakeCounter(init=0) {
let val = init;
function counter() { return val++; }
return counter
}
let ctr = MakeCounter(10)
ctr() // 10
ctr() // 11
ctr() // 12
模拟私有方法
由于闭包环境变量的私有性,容易想到用来模拟一些私有方法。
下面一个相对复杂的计数器例子中:私有属性 privateVal
只能通过公共方法 getValue
进行访问;私有方法 changeBy
也只能通过其他公共方法调用。
function MakeCounter(init) {
let privateVal = init;
function changeBy(delta) { privateVal += delta; };
return {
getValue() { return privateVal; },
increment() { changeBy(1); },
decrement() { changeBy(-1); },
plus(b) { changeBy(b); },
minus(b) { changeBy(-b); },
}
}
let ctr = MakeCounter(10);
ctr.value() // 10
ctr.increment()
ctr.plus(9)
ctr.value() // 20
多个闭包共用一个环境?
将上面计数器的例子小改一下,在返回的对象里增加一个值。
返回时一个对象,包括一个值和五个函数:
- 五个函数共用一个环境,影响同一个环境;
- 值
val
,在完成返回后,脱离该环境,独立成为一个变量/属性。
function MakeCounter(init) {
let privateVal = init;
function changeBy(delta) { privateVal += delta; };
return {
val: PrivateVal,
getValue() { return privateVal; },
increment() { changeBy(1); },
decrement() { changeBy(-1); },
plus(b) { changeBy(b); },
minus(b) { changeBy(-b); },
}
}
let ctr = MakeCounter(10);
ctr.getValue() // 10
ctr.val // 10
ctr.increment()
ctr.plus(9)
ctr.getValue() // 20
ctr.val // 10
ctr.val = 100
ctr.val // 100
性能考量
- 闭包在处理速度和内存消耗方面对脚本性能具有负面影响;
- 闭包特性能达到的效果,使用高级语法也能实现;
- 高手总是会闭包。