谈到js,必然逃不了闭包。
闭包到底是啥呢?我查了不少资料,解释真的是各种各样,千奇百怪,令人困惑。
我们先来看看一下各种解释
- 红宝石书:闭包指的是那些引用了另一个函数作用域中变量的函数。
- mdn : 闭包(closure)是一个函数以及其捆绑的周边环境状态的引用的组合
- 其它 : 闭包就是指有权访问另一个函数作用域中的变量的函数
这可以说是我们最常见的几种说法。
第一和第三种说法,我们把句子整合一下,就是说闭包是个函数,这很明显是错的。
然后再来说说我们常看见的闭包
function func() {
const a = 1;
return () => a;
}
const oFunc = func();
我相信大多数人一开始见到的闭包都是这样
一个内部函数引用了外部函数作用域的变量,然后这个函数被保存函数外部。
作用域
首先说一句,闭包一定跟作用域有关。
作用域有块级作用域,函数作用域,全局作用域。
函数之所以能访问到外部作用域中的变量和函数,正是因为有作用域链的存在。
作用域是当前的执行上下文,值 和表达式在其中“可见”或可被访问。如果一个变量 或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。
一句话内层作用域能访问外层作用域的东西。
这跟一个东西有关。叫作用域链。
作用域链[[scope]]
其实就是包含指针的列表,可以看作是一个数组,每个指针都指向一个变量对象。
这里的变量对象又牵扯到另一个东西叫做执行上下文。
执行上下文
执行上下文是动态的,当函数或程序执行时,会创建自己的执行上下文推入执行上下文栈中,执行上下文可以认为是代码的一个执行环境,所有代码都是在执行上下文环境中执行的。
JavaScript执行上下文有全局执行上下文,和函数执行上下文。
执行上下文有一个变量对象,全局执行上下文的变量对象叫做全局上下文对象,函数执行上下文的变量对象叫做活动对象。
当一个函数要执行时,会把它的执行上下文推入调用栈,调用栈始终执行栈顶的代码,当函数的代码执行完成后就会把函数的执行上下文弹出栈顶。因为js最开始是进入全局环境,所以调用栈的栈底是全局执行上下文。
变量对象在执行上下文中。
以下面的代码为例
我们来看看[[scope]]
function wrapper() {
function inner() { }
}
inner
执行时的作用域链
wrapper
执行时的作用域链
从作用域链来看,外部函数的作用域链里没有内部函数的变量对象,自然访问不到内部函数的任何东西,这也是在情理之中。
上面wrapper
执行时的作用域链也可以认为是inner
定义时的作用域链。
前置知识已经了解。
那么什么是闭包呢?以下都是我的认为,不敢说百分百正确。
闭包
首先,闭包一定不是一个函数。我更喜欢说,它是内部函数访问其它函数作用域里变量或函数的一种现象。
首先说一下我在写这篇文章前一段时间对闭包的一个认识。
只要在函数内部用到了其它作用域的变量那么就会产生闭包。
这句话不能说他是错的,而且也是我认为最接近闭包的一个解释。
最具权威的就是mdn了。
我们直接上代码。
let g = 10;
function a() {
let a1 = 10, a2 = 20, a3 = () => { }; // 外层函数作用域
let yx = 10;
{ // 块级作用域
let block1 = 10;
let block2 = 20
(function (e) {
g; // 用到了全局作用域里的变量
a1; // 用到了a函数作用域的变量
a2; // 用到了a函数作用域的变量
a3(); // 用到了a函数作用域的函数
block1; // 用到了块级作用域的变量
block2; // 用到了块级作用域的变量
console.dir(arguments.callee); // 打印函数本身的引用
})(5);
}
// 函数并没有保存到外部 而是直接执行了
}
a();
控制台打印结果
我们看到一个很眼熟的东西Closure
闭包(closure)是一个函数以及其捆绑的周边环境状态的引用的组合
这不就是mdn上的一个解释吗?
我们看到它保存了内部函数引用到的外层函数作用域里的几个变量和函数,a1,a2,a3。
但它却没有包含yx
。
很显然闭包只保存了用到的变量和函数,而且这些变量和函数所存在的作用域应该是函数作用域。
在打印结果中
块级作用域引用到的变量被保存在Block
全局作用域引用到的变量被保存在Script
其实这里我在想,Block
和Script
算不算是闭包的另一种表示。
闭包是什么呢?
mdn : 闭包(closure)是一个函数以及其捆绑的周边环境状态的引用的组合
从直观上看,闭包其实就是一个对象,这个对象以key-value的形式存储了函数用到的其它函数作用域里的变量和函数,并且与函数捆绑在一起,使得函数无论在什么地方被调用,都能访问到这些变量和函数,并且在函数存在期间,闭包也会存在,这也使得闭包内的变量和函数是可以访问的,导致了这些变量和函数不会GC回收。
上面的解释,我说的比较通俗。
下面的可以说是mdn的另一个解释吧
闭包是其实就是将函数与这个函数用到的其它(函数)作用域里变量或函数进行捆绑的一种现象,因为他们捆绑在一起了,所以当这个函数还能访问到的情况下,与它捆绑在一起的变量和函数都不能被垃圾回收器回收,所以我们也经常说,闭包容易导致内存占用。
正常情况下产生的闭包,是不会有内存占用的危险,比如上面的代码,函数执行完后,其它地方根本不能访问到这个函数,所以函数会被垃圾回收器回收,与之形成闭包的变量和函数也将被释放。
上面函数我之所以打上括号,是因为我不太清楚是不是仅包括函数作用域。
但是既然mdn上的解释只是强调周边环境,那应该是包括了块级作用域和全局作用域。
好了我的看法大概就是这些。
有位网友说的很好,也很容易懂。我们来看一下。
其实他的解释跟我的解释差不多,只不过他解释的更通俗易懂。
闭包的意义
其实闭包的意义
我想说就是因为有闭包所以我们的函数才能在任何地方都能访问与它捆绑在一起的变量和函数。换句话来说就是将被引用的变量的生命周期延长至与函数的生命周期一样。
所以闭包会导致内存占用,其实确切来说,在你不需要那个函数的时候,那个函数你没有将它销毁。而我们要用到的函数,那其实这样的内存占用是合理的。
然后就是闭包在我们开发中的意义
私有化变量这些大家应该都懂。我觉得没必要讲。
以上都是我的见解。