闭包
当程序调用一个函数时,会发生什么?
1JavaScript创建一个新的执行上下文,我们叫作本地执行上下文。
2这个本地执行上下文将有它自己的一组变量,这些变量将是这个执行上下文的本地变量。
3新的执行上下文被推到到执行堆栈中。可以将执行堆栈看作是一种保存程序在其执行中的位置的容器。
函数什么时候结束?当它遇到一个return
语句或一个结束括号}
。
当一个函数结束时,会发生以下情况:
1这个本地执行上下文从执行堆栈中弹出。
2函数将返回值返回调用上下文。
3这个本地执行上下文被销毁,销毁是很重要,这个本地执行上下文中声明的所有变量都将被删除
因此引出变量的作用域
1. 变量的作用域
根据作用域的不同,JavaScript 中的变量可分为两种:全局变量和局部变量。
其中,函数内部可以直接读取全局变量:
- 全局执行上下文未删除
但是,函数外部不能读取函数内的局部变量:
2. 什么是闭包
以下摘自MDN:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
仅函数存在闭包,对象不存在闭包(访问不到词法环境)。
function fn1() {
let a = 1;
return function fn2() {
console.log(a);
};
}
// 因为fn1函数有返回值,用result接收
const result = fn1(); // result存储函数定义和闭包(a=1)
result(); // 1
//仅函数有闭包,对象没有
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {func1()//func1 is not defined
setTimeout( () => {
this.func1()//cherry
},1000);
}
};
a.func2()
1.无论何时声明新函数并将其赋值给变量,都要存储函数定义和闭包。闭包包含在函数创建时作用域中的所有变量,它类似于背包。函数定义附带一个小背包,它的包中存储了函数定义创建时作用域中的所有变量。
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add5 = makeAdder(5);//
const add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
个人理解:执行makeAdder函数时创建一个新的本地执行上下文,里面含变量x=5和函数function,执行完后将function(y)返回,之后执行上下文销毁,但add5的实例维持了对它语法环境的引用(x存在其中),故add5调用时,变量x仍可用
add5
和add10
都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在add5
的环境中,x
为 5。而在add10
中,x
则为 10。
3. 闭包的作用
1. 在函数外部访问到函数内部变量
2. 使变量留存在内存中
闭包另一个作用,使已经运行结束的函数上下文中的变量对象继续留在内存中。看下面一段 demo:
function fn1() {
var a = 1;
return function fn2() {
console.log(a++);
};
}
var result = fn1();
result(); // 1
result(); // 2
result(); // 3
上述代码执行 3 次result()
后,分别输出了a
的值,a
不断地+1
。这说明函数fn1
中的变量a
一直保存在内存中,并没有在函数fn1
调用后被清除。
因为函数fn2
被赋给了一个全局变量,因此fn2
会一直在内存中,fn2保存了fn1的语法环境,故a并不会被
垃圾回收机制清除。
3. 模拟私有方法
const counter = function () {
let privateCounter = 0;
return {
add() {
privateCounter++;
},
min() {
privateCounter--;
},
value() {
return privateCounter;
},
};
};
const counter1 = counter();
const counter2 = counter();
counter1.add();
counter1.add();
counter1.add();
counter2.min();
console.log(counter1.value()); // 3
console.log(counter2.value()); // -1
两个计数器counter1
和counter1
都保持各自的独立性。每个闭包都是引用自己作用域内的变量privateCounter
,每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。另外,在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。
4. 闭包的副作用
1. 常驻内存
闭包会使得函数中的变量都被保存在内存中,内存消耗很大
参考
我从来不理解JavaScript闭包,直到有人这样向我解释它