JavaScript闭包
前言
由于JavaScript的闭包(closure)是其最强大的特性,jQuery、Vue.js库都使用了闭包特性来实现,本篇文章总结了一下博主自己对JavaScript闭包特性的学习和理解
闭包
闭包的例子
我们先简单展示一个闭包的代码,让大家先观察一下闭包代码的形式
1 |
|
上述代码中,func即为闭包
闭包的定义
不同作者对于闭包的定义都有不同的描述,理解其核心在于记住产生闭包的时机
内层的作用域访问它外层函数作用域里面的参数/变量/函数时,闭包就产生了
闭包也是一种作用域
在chrome浏览器“开发者工具”的控制台可以看到闭包出现在Scope
一栏,因此闭包也是一种作用域闭包是一种作用域,它拷贝了一套外层函数作用域中被访问的参数、变量/函数,这个拷贝都是浅拷贝(引用)
闭包的优点
访问其他函数内部的变量
1
2
3
4
5
6
7
8
9function outer() { // outer为闭包
var a = '变量1'
var inner = function () {
console.info(a)
}
return inner // inner访问了outer函数作用域中的变量a
}
var inner = outer()
inner() //"变量1"上述代码中,
inner
函数作用域访问了外层作用域函数outer
中的变量a
闭包内部的变量无法被外部作用域访问和修改,可以实现软件设计上的封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//定义一个模块
function module(n) {
//私有属性
let name = n;
//私有方法
function getModuleName() {
return name;
}
//私有方法
function someMethod() {
console.log("coffe1891");
}
//以一个对象的形式返回
return {
getModuleName: getModuleName,
getXXX: someMethod
};
}
let myapp = module("myModule");//定义一个模块
console.log(myapp.getModuleName()); //>> myModule
console.log(myapp.getXXX()); //>> coffe1891上述代码中,变量
name
、函数getModuleName
与someMethod
类似高级语言的私有属性和方法,无法被外部作用域访问和修改(除非提供返回的对象接口),只有module
内部作用域可以访问,实现了设计上的“封装”保护变量不被内存回收机制回收
1
2
3
4
5var report = function(src) {
var img = new Image();
img.src = src;
}
report('http://www.xxx.com/getClientInfo');//把客户端信息上报数据上述用于数据统计上报的代码,会丢失部分数据上报。原因是
Image
对象是report
函数中的局部变量,当report
函数调用结束后,Image
对象随即被JS引擎垃圾回收器回收,而此时可能还没来得及发出http请求,导致上报数据请求失败1
2
3
4
5
6
7
8
9var report = (function() {
var imgs = [];//在内存里持久化
return function(src) {
var img = new Image();
imgs.push(img);//引用局部变量imgs
img.src = src;
}
}());
report('http://www.xxx.com/getClientInfo');//把客户端信息上报数据使用闭包把
Image
对象封装起来,就可以解决数据丢失问题。此时,imgs
变量被report
函数作用域链所引用,不会在IIFE函数执行完成后,因为退出函数调用栈而被JS引擎垃圾回收器收回
闭包的缺点
- 过渡使用闭包会占用过多内存,甚至引起内存泄漏上述代码中,当执行完
1
2
3
4
5
6
7
8
9
10
11
12function A(){
var count = 0;
function B(){
count ++;
console.log(count);
}
return B;//函数B保持了对count的引用
}
var b = A();
b();//>> 1
b();//>> 2
b();//>> 3var b = A();
之后,A函数的执行环境并没有被销毁,其中count
变量被b
的函数作用域链所引用,并没有因为函数A执行完毕退出函数调用栈而被JS引擎垃圾回收器回收,直至三次调用b()
之后,并且删除变量b或赋值为null,b
和A
的执行环境才会被销毁JavaScript中的垃圾回收规则:如果对象不再被引用,或者对象互相引用形成数据孤岛后且没有被孤岛之外的其他对象引用,那么这些对象将会被JS引擎的垃圾回收器回收;反之,这些对象一直会保存在内存中
避免闭包内存泄漏的方法
- 避免闭包导致内存泄漏的解决方法是,在函数A执行完毕退出函数调用栈之前,将不再使用的局部变量全部删除或者赋值为null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16这段代码会导致内存泄露
window.onload = function(){
var el = document.getElementById("id");
el.onclick = function(){
alert(el.id);
}
}
解决方法为
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循环引用
el.onclick = function(){
alert(id);
}
el = null; // 将闭包引用的外部函数中活动对象清除
}
闭包的写法
循环中的闭包
1
2
3
4
5
6
7for (var i = 1; i <= 5; i++) {
(function(j) {//包了一层IIFE形式的函数,这个函数是闭包
setTimeout(function test() {//函数体内的j引用了外层匿名函数的参数j
console.log(j); //>> 1 2 3 4 5
}, j * 1000);
})(i);
}模块化封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//定义一个模块
function module(n) {
//私有属性
let name = n;
//私有方法
function getModuleName() {
return name;
}
//私有方法
function someMethod() {
console.log("coffe1891");
}
//以一个对象的形式返回
return {
getModuleName: getModuleName,
getXXX: someMethod
};
}
let myapp = module("myModule");//定义一个模块
console.log(myapp.getModuleName()); //>> myModule
console.log(myapp.getXXX()); //>> coffe1891返回新函数
1
2
3
4
5
6
7
8
9
10
11
12function sayHello2(name) {
var text = "Hello " + name; // 局部变量
var sayAlert = function() {
console.log(text);
};
return sayAlert;
}
var say2 = sayHello2("coffe1891");
say2(); //>> Hello coffe1891调用
sayHello2()
函数返回了sayAlert
,赋值给say2
。say2
是一个引用变量,指向一个函数本身,而不是指向一个变量扩展全局对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function setupSomeGlobals() {
//私有变量
var num = 666;
gAlertNumber = function() {//没有用var和let关键字声明,会成为全局对象的方法
console.log(num);
};
gIncreaseNumber = function() {
num++;
};
gSetNumber = function(x) {
num = x;
};
}
setupSomeGlobals();
gAlertNumber(); //>> 666
gIncreaseNumber();
gAlertNumber(); //>> 667
gSetNumber(1891);
gAlertNumber(); //>> 1891三个全局函数
gAlertNumber
,gIncreaseNumber
,gSetNumber
指向了同一个闭包,因为它们是在同一次setupSomeGlobals()
调用中声明的。它们所指向的闭包是与setupSomeGlobals()
函数关联一个作用域,该作用域包括了num
变量的拷贝。也就是说,这三个函数操作的是同一个num
变量延长局部变量生命
1
2
3
4
5
6
7
8
9var report = (function() {
var imgs = [];//在内存里持久化
return function(src) {
var img = new Image();
imgs.push(img);//引用局部变量imgs
img.src = src;
}
}());
report('http://www.xxx.com/getClientInfo'); //把客户端信息上报数据闭包把
Image
对象封闭起来,就可以解决数据丢失的问题
参考资料
闭包造成的内存泄露如何解决:https://www.cnblogs.com/yanjianjiang/p/13881231.html
面试时高频问到的“闭包”:https://coffe1891.gitbook.io/frontend-hard-mode-interview/1/1.2.5
本文作者: 贾明晖
本文链接: http://minghuijia.cn/2022/04/25/JavaScript%E9%97%AD%E5%8C%85/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!