前言
有关闭包的文章,网上的讲解可以说是非常多,但并不是很容易读懂。一是因为闭包概念过于抽象;二是好多文章把闭包讲解的过于复杂。这篇文章讲解一下我对闭包的理解和总结,希望大家能够对闭包产生新的认识,如果有不对的地方欢迎指正。
全文概要
1 理解闭包
很多编程语言都支持闭包,闭包不是语言特性,而是一种编程习惯。闭包(Closure)是指具有一个封闭对外不公开的包裹结构,或空间。
在JS中,函数可以构成闭包,因为外部是不能访问到函数内部数据,对外封闭不公开,且函数通常是包裹一段代码。我们可以理解为闭包是函数在特定情况下执行产生的一种现象。
那么怎样的情况会产生闭包呢?即产生闭包必满足三个条件:函数嵌套、内部函数引用外部函数数据、外部函数调用,凡是所有的闭包都满足以上三个条件,否则不构成闭包。
闭包到底是什么?我认为,所谓闭包,是一种引用关系,该引用关系存在内部函数中,内部函数引用外部函数数据,引用的数据可以在函数词法作用域(函数外部)之外使用。
2 解决的问题
我们都知道,根据作用域查找规则,外部是不能访问到函数内部数据,如果我们想访问函数内部数据,可以借助函数中的return
:
function f1(){
var num = 10;
return num;
}
var res = f1();
console.log(res); // 10
借助return
可以访问到函数内部数据,但存在一个问题:数据不能被二次访问。因为第二次访问时候是再次调用该函数,函数中的代码才会再次返回,这个我们通过生成随机数可以很好的证明:
function f1() {
var num = Math.random();
return num;
}
var res = f1();
var res2 = f1();
console.log(res + '\n' + res2);
输出结果如下,我们发现两次的输出结果并不一样:
无论我们怎样执行,两次的随机数结果都不同,这种输出结果显然不好。如果我们想让函数只执行一次,我们该怎么做呢?我们可以在f1
函数中嵌套一个函数,嵌套的内部函数是可以访问f1函数变量的。
function f1(){
var num = Math.random();
function f2(){
return num
}
return f2
}
var f = f1();
var res1 = f();
var res2 = f();
console.log(res + '\n' + res2);
此时的输出结果如下:
这就产生了闭包,我们试着分析一下这段代码:
-
全局
f1
函数在0级作用域链上,f1
函数是一个一级链,f1
函数中有一个变量num
,还有一个函数体f2
。 -
f2
是二级链,通过return将f2
当做一个值返回给f1
函数。 -
f1
函数执行后,将f2
的引用赋值给f
,执行f
函数,输出num
变量。
正常来说,当f1函数调用完毕,其作用域是被销毁的,而通过闭包我们将f2给了f,f2函数内仍然持对num的引用,num仍然存活内存中,延长了内部函数局部变量生命周期。在当f调用,num是可以访问到的。
其实,闭包也就是使用了链式访问技巧,0级链无法访问一级链数据,我们通过间接0级链操作二级链的函数,来访问一级链数据。
闭包解决的问题是:让函数外部访问到函数内部的数据。
3 闭包本质
再看上面这段代码,我们来分析闭包是如何产生的:
function f1() {
var num = Math.random();
function f2() {
return num;
}
return f2;
}
f1()
我们通过chrome调试工具查看这段代码,发现当代码运行到外部函数f1定义时,就会产生了闭包:
闭包(Closure)到底是个啥?闭包本质:
内部函数里的一个对象,这个对象非Js对象(有属性有方法的对象),这个对象是函数在运行时,本该释放的活动对象,这个活动对象里包含着我们引用的变量。
4 基本结构
上面我们说了,闭包就是间接获得函数内部数据使用权利,我们可以总结出常见的闭包结构。我们在日常开发中见到的绝大多数闭包结构,都是基于以下有三种。
4.1 return另一函数
写一个函数,函数内部定义一个新函数,返回新函数,用新函数获得函数内部数据。
function f1(){
var a = 0
function f2(){
a++
console.log(a)
}
return f2
}
var f = f1();
f(); // 1
f(); // 2
4.2 return绑定多个函数的对象
写一个函数,函数内定义一个对象,对象中绑定多个方法,返回对象,利用对象的方法访问函数内部数据。
eg : 如何获得超过一个数据?
function f1(){
var num1 = Math.random();
var num2 = Math.random();
return{
num1:function(){
return num1;
},
num2:function(){
return num2
}
}
}
f1()
// {num1: ƒ, num2: ƒ}
f = f1()
f.num1()
f.num2()
eg: 如何读取一个数据和修改一个数据?
function f1(){
var num =Math.random();
return {
get_num:function(){
return num;
},
set_num:function(value){
// 此时num访问的是f1函数中的num
num = value;
}
}
}
var f = f1();
// 读取函数中的值
var num = f.get_num();
console.log(num);
// 0.3919299622715364
// 设置函数中的值
f.get_num(123);
num = f.get_num();
console.log(num);
//123
4.3 将函数实参传递给另一函数
函数的实参,也就是函数中局部变量。
function delay(msg){
setTimeout(function(){
console.log(msg)
},2000)
}
delay('开启计时器')
5 总结
- 闭包:具有对外封闭不公开的包裹结构或空间,函数可以构成闭包。
- 闭包产生三要素:函数嵌套、内部函数引用外部函数数据、外部函数调用。
- 所谓闭包,是一种引用关系,该引用关系存在内部函数中,内部函数引用外部函数数据,引用的数据可以在函数词法作用域(函数外部)之外使用。
- 解决的问题:间接访问函数中的数据、延长内部函数局部变量的生命周期。
- 闭包本质:内部函数里的一个对象,这个对象非Js对象(有属性有方法的对象),这个对象是函数在运行时,本该释放的活动对象,这个活动对象里包含着我们引用的变量。
- 基本结构:rerurn另一函数、return绑定多个函数的对象、将函数实参传递给另一函数。
结语
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤,写作不易,持续输出的背后是无数个日夜的积累,您的点赞是持续写作的动力,感谢支持。