JS原理 - 理解闭包
闭包的概念以及相关知识点
概念
闭包的本质就是:当前环境存在指向父级作用域的引用。
在函数内部声明一个新的函数(即内部函数),内部函数引用了其父函数的变量,并将内部函数作为值返回(return),在父函数被调用时就会形成闭包。
function foo(){
var name = 'lili'; // 父函数的局部变量
function child(){
console.log(name); // 引用父函数中声明的变量
};
return child;
}
const x = foo();
x();
是不是只有返回一个函数的形式的才是闭包呢?
不,只需要存在指向父级作用域的引用的即可,举个例子:
var fun3;
function fun1() {
var a = 2
fun3 = function() {
a++;
console.log(a);
}
}
fun1();
fun3(); // 3
fun3(); // 4
作用
访问内部变量
闭包可以保持对作用域的引用,让外部函数能够访问内部函数的变量。
打个比方,有一根金条放在邻居家里,你不能入室去偷这根金条,但是你可以通过贿赂邻居家的小孩,让他帮你把金条从房子里拿出来,你就得到这根金条了。
function foo(increment){
let counter = 0;
function child(){
counter += increment;
console.log(counter);
};
return child;
}
const _child = foo(1);
_child(); // 1
_child(); // 2
_child(); // 3
console.log(counter); // error:counter is not defined
如果直接打印 counter 一定会得到counter is not defined
,因为无法访问 foo 函数内部的变量,但通过闭包的形式,却可以打印出变量 counter 的值。
保留变量的引用
而且 counter 能够实现累加,因为闭包保持了 foo 作用域中的变量(counter)和传入的参数(increment)的引用,除非手动销毁,不然作用域会一直保存在内存当中。
然后来看一个特殊情况,在返回的 child 函数中再套一个函数 inner,inner 打印出 child 函数里的变量 msg:
function foo(increment){
let counter = 0;
function child(){
counter += increment;
console.log(counter);
const msg = `counter is ${counter}`;
return function inner(){
console.log(msg);
}
}
return child;
}
const _child = foo(1);
const _inner = _child(); // 1
_child(); // 2
_child(); // 3
_inner(); // counter is 1
从下图可以看到,出现了两个 Closure(闭包):
如果要让 counter 获取到最新的值,有两种解决方式:
改变调用
_child()
的位置:function foo(increment){ let counter = 0; function child(){ counter += increment; console.log(counter); const msg = `counter is ${counter}`; return function inner(){ console.log(msg); } } return child; } const _child = foo(1); _child(); // 1 _child(); // 2 const _inner = _child(); // 3 _inner(); // counter is 3
这样虽然还是产生了闭包,但是闭包内的 counter 值是最新的。
将 msg 移入至 inner 函数内,这样 child 函数和 inner 函数就不存在引用关系,就无法形成闭包。
function foo(increment){ let counter = 0; function child(){ counter += increment; console.log(counter); return function inner(){ const msg = `counter is ${counter}`; console.log(msg); } } return child; } const _child = foo(1); const _inner = _child(); // 1 _child(); // 2 _child(); // 3 _inner(); // counter is 3
从下图可以看到,在运行最后的 _inner() 时,并没有形成两个 Closure(闭包):
从上述的例子可以看出,并不是所有的函数都能形成闭包,需要子函数有父函数变量的引用才行。
实际运用
防抖节流
function debounce(fn, delay = 300) {
let timer; //闭包引用的外界变量
return function () {
const args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
JS原理 - 理解闭包