闭包在程序中无处不在,通俗来讲
闭包就是一个函数对其周围状态的引用并捆绑在一起的一种组合
,你也可以理解成一个函数被引用包围
或者是一个内部函数能够访问外部函数的作用域
闭包
是面试经常考的,也是了解一个程序员基础知识一个重要点,本篇是笔着对于闭包
的理解,希望在实际项目中有所思考和帮助。
正文开始...
闭包是什么
我们可以从以下几点来理解
- 闭包是
一个函数
对其周围状态
的引用
并捆绑在一起的一种组合
一个函数
被引用
包围一个内部函数
能访问外部函数
的作用域
我们来看一张图理解下上面三句话
对应代码如下
function A() {
var name = 'Maic',
age = 0;
function B() {
console.log(name, age);
}
}
A();
// console.log(name) name is not defined
我们注意到在A
函数中,我们创建了两个内部的私有变量name
、age
,并且我们在A
函数中创建一个内部函数B
,此时在B
函数中,我们会发现在B
内部可以访问它周围状态(变量)
,也就意味着在B
函数内部可以访问外部函数
的作用域。
至此你会发现,闭包
就是在B
函数一创建,并且有对周围的状态
有引用,那么此时闭包
就出现了,也就是说,闭包就是一座桥梁,能让B
函数内部能突破自身作用域
访问外部
的变量。
不知道你有没有发现,我在A
内部定义的变量,我在外部并不能访问,也就是说相对A
的外部,A
内部所有的变量都是私有的
,在A
定义的变量,相对于B
中,又可以访问。因为B
函数能访问A
中的变量,也正是依靠闭包
这座桥梁。
闭包的特性
1.创建私有变量
2.延长变量的生命周期
我们知道闭包
会造成内存泄露,本质上就是创建的变量一直在引用内存中,当一个普通函数被调用结束时,函数内部创建的变量就会被销毁。
但是闭包会保存其变量的引用,即便外部执行上下文被销毁,但是闭包内创建的词法环境依然还在,我们看下面代码具体理解下。
function A() {
var name = 'Maic',
age = 0;
function B() {
age++;
console.log(name, age);
}
return B;
}
var b1 = A();
b1(); // 1
b1(); // 2
b1(); // 3
在A
中返回了函数B
,实际上就是返回了一个函数,当我们我们用var b1 = A()
申明一个变量时,实际上,这里内部B
还没有执行,但是在执行A()
方法时,返回的是一个函数,所以我们继续执行b1()
,我们尝试调用三次,我们会发现打印出来的值是1,2,3
,这就说明,闭包
延长了变量的生命周期,因为第三次与第二次打印出来的值就是同一个值的引用。 具体一张图可以可以理解下
当我们用var b1 = A()
时,实际上,我用蓝色的方框已经标注起来了,在b1
内部我们可以看到,每执行b1
,实际就是执行的红色
区域的函数,也就是A
内部定义的函数B
,但是每次调用b1
,我们看到都有保留age
的引用,所以你看到age
依次就是1,2,3
,所以也就证实了闭包能延长变量的生命周期
,并且闭包创建的私有变量
可以减少全局变量的使用。
通常我们知道尽量少创建全局变量,因为我们不知道这个全局变量什么时候使用,只有在被使用的时候才会被释放。闭包也是解决了全局变量命名冲突的问题
,因为创建的私有变量,没法在外部访问,这样也就减少了变量名
污染的问题。
等等,还有一个问题,如果我把上面的代码改成下面呢?
function A() {
var name = 'Maic',
age = 0;
function B() {
age++;
console.log(name, age);
}
return B;
}
A()(); // 1
A()(); // 1
// var b1 = A();
// b1();
// b1();
// b1();
// console.log(name)
你会发现,我两次打印的都是同一个1
,这是为什么呢?你有没有发现之前我们是用var b1 = A()
申明的一个变量,实际上这句代码就是js
新开辟暂存了一块空间,因为A
内部返回的是一个函数,当我每次调用b1
时,实际上是调用返回的那个函数,因为函数内部存在闭包的引用,所以一直就1,2,3
,但是我这里我使用的是A()()
,我们发现每次都是1
,说明当我第二次调用时内部的age
已经重新定义了一遍,而并没有引用上一次的值,这就说明,在A()
立即调用时,闭包内部引用的变量已经被释放。由于闭包
也会有缺陷,创建太多的闭包会造成消耗内存严重,影响网页性能。
应用场景
柯里化函数
回调函数
计数器
、延迟调用(防抖与节流)
- 柯里化函数
实际上就是把一个函数的多个参数拆分成多个函数调用,主要目的是避免平繁调用具有多个相同参数函数,又可以复用相同函数
,具体可以用下面代码理解下
// 未柯里化之前
function sum(a, b, c) {
return a + b + c;
}
sum(1, 2, 3);
函数柯里化后
function sum(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
const a = sum(1);
const b = a(2);
const c = b(3);
console.log(c); // 6 or sum(1)(2)(3)
上面 🌰 好像还是不太明显,在具体业务中,你可能会写出这样的代码
// 根据正则规则校验某个字段
function regKey(reg, val) {
return reg.test(val);
}
var isPhone = regKey(/^1[3,5,7,8,9]\d{9}$/, 13754123124);
const isNumber = regKey(/\d/, 'test');
改成函数柯里化后
function regKey(reg) {
return (val) => {
return reg.test(val);
};
}
const checkPhone = regKey(/^1[3,5,7,8,9]\d{9}$/);
const checkNum = regKey(/\d/);
const isPhone = checkPhone(13754123124); // true
const isNumber = checkNum(123); // true
我们发现改完后,貌似柯里化后,代码反而变多了,但是代码的可读性以及拓展性比以前更友好,这点因特殊业务功能而定,也不是非要把用柯里化函数去处理所有的业务,具体因情况而定,这里只是举了个简单的例子。
- 回调函数
回调函数
在业务中使用的太多了,具体可以看下下面这个简单的例子,写一段为伪代码感受一下
const request = (params) => {
const response = {
code: 0,
success: '成功',
data: []
};
return (callback) => {
callback(response);
};
};
const queryList = () => {
const getData = request({ pageIndex: 1, pageSize: 10 });
getData((res) => {
console.log(res); // {code: 0, success: '成功', data: []}
});
};
- 计数器
这个就非常典型了,比如说一个循环里面
for (var i = 0; i < 10; i++) {
(function () {
var j = i;
setTimeout(() => {
console.log(j);
}, i * 1000);
})();
}
- 函数节流
频繁触发事件,在指定一段时间内调用函数
// 模拟数据请求伪代码
const fetchList = () => {};
let flag = false;
window.addEventListener('scroll', () => {
if (flag) {
return;
}
flag = true;
setTimeout(() => {
flag = false;
fetchList();
}, 500);
});
- 函数防抖
利用定时器做缓冲器,当第二次调用时,清除上一次的定时器,在指定时间内重新调用函数
// 模拟数据请求伪代码
const fetchList = () => {};
const inputDom = document.getElementById('input');
let timer = null;
inputDom.oninput = function () {
if (!timer) {
clearTimeout(timer);
timer = setTimeout(() => {
fetchList();
}, 500);
}
};
以上案例都有用到闭包
,闭包的身影无处不在,只是我们用的时候,我们并没有发现。
总结
闭包
的概念,闭包是一个函数
对其周围状态
的引用
并捆绑
在一起的一种组合,或者是一个函数
被引用
包围,或者是一个内部函数
能访问外部函数
的作用域闭包的特性,
创建私有变量
和延长变量的生命周期
闭包的应用场景,
柯里化函数
、回调函数
、定时器
、节流/防抖
等本文示例code example
最后,喜欢的点个赞,在看,转发,收藏等于学会,欢迎关注 Web 技术学苑,好好学习,天天向上!