3.闭包 - JS

作用域

一般认为 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

性能考量

  1. 闭包在处理速度和内存消耗方面对脚本性能具有负面影响;
  2. 闭包特性能达到的效果,使用高级语法也能实现;
  3. 高手总是会闭包。

你可能感兴趣的:(#,JS,函数进阶,javascript,前端,开发语言,js,ecmascript,笔记,学习)