【js重学系列】闭包
闭包概念
MDN 上这样定义闭包:闭包是函数和声明该函数的词法环境的组合。
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
闭包产生的本质,当前环境中存在指向父级作用域的引用。
闭包的表现形式
返回一个函数
// 案例1
function c() {
var d = 1;
console.log("d:", ++d);
d = d + 5;
return function () {
console.log("此时d的值:", ++d);
};
}
c(); //d: 2
c(); //d: 2
var e = c(); //d: 2
e(); //d: 8
// 案例2
var x = 1;
function demo() {
var x = 2;
function demo2() {
console.log(x);
}
return demo2;
}
var h = demo();
h(); // 2
// 当调用demo后,会返回一个函数,此时全局变量 h接受这个函数,因为demo2存在指向父级作用域的引用,h变量会拿到父级作用域的变量,demo2恰恰引用了window demo 和 本身的作用域。 因此demo2可以访问到demo作用域中的变量。
作为函数参数传递
var a = 1;
function foo() {
var a = 2;
function baz() {
console.log(a);
}
bar(baz);
}
function bar(fn) {
// 这就是闭包
fn();
}
// 输出2,而不是1
foo();
闭包实质
问题来了? 是不是只有返回函数才会产生闭包呢?
回到闭包实质:只需要让父级作用域的引用存在即可
当函数能够记住并访问所在的词法作用域时,就产生了闭包。
var demo2;
function demo() {
var x = 2;
demo2 = function () {
console.log(x);
};
}
demo();
demo2(); // 2
//首先让外部函数执行,给 demo2 赋值,等于 demo2 现在拥有了 window demo 和 自身的作用域的访问权限,那么查找变量 x 的时候,会逐级的向上去找,在最近的 demo 作用域找到标示符就返回结果,输出 2。
// 🤭 在这里外部的变量 demo2 存在父级作用域的引用,因此产生了闭包,形式变了,本质还是没有改变。
闭包的应用
防抖节流函数
单例模式
js最初版模块化
在定时器、事件监听、Ajax 请求、跨窗口通信、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包
以下的闭包保存的仅仅是 window 和当前作用域。
// 定时器
setTimeout(function timeHandler(){
console.log('2222');
},100)
// 事件监听
$('#div').click(function(){
console.log('DIV Click');
})
IIFE(立即执行函数表达式)创建闭包, 保存了全局作用域 window 和当前函数的作用域,因此可以全局的变量。
var x = 22;
(function IIFE() {
// 输出22
console.log(x);
})();
var arr = [1, 2, 4, 6, 2, 8, 9];
function bet(a, b) {
return function (v) {
return v >= a && v <= b;
};
}
console.log(arr.filter(bet(2, 5))); //[2,4,2]
// 为什么说这是利用了闭包特性呢,1函数嵌套函数,1子函数里有对父函数变量的引用,bet(2,5)调用后就返回的是子函数,但是子函数有对父函数实参的引用,
闭包的优点
让外部作用域访问内部变量
保存变量不被销毁
闭包的缺点
会造成内存泄漏的问题
解决方法 把元素设置为 null
this 指向问题
let obj2 = {
user: 123,
get() {
return this.user;
},
};
console.log(obj2.get()); //123 this指向obj2
let obj2 = {
user: 123,
get() {
return function () {
return this.user; //未定义 this指向window
};
},
};
console.log(obj2.get()());
// 解决方法:
let obj2 = {
user: 123,
get() {
// console.log(this); //obj2
let that = this;
return function () {
return that.user; //未定义 this指向window
};
},
};
console.log(obj2.get()());
// 或者用箭头函数
链接:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
https://blog.csdn.net/weixin_43586120/article/details/89456183
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://www.cnblogs.com/sandaizi/p/11582488.html
https://zhuanlan.zhihu.com/p/22486908
https://juejin.cn/post/6844904014232944648#heading-9