闭包
在理解闭包之前,有个重要的概念需要先了解一下,就是 js 执行上下文
JS代码在执行前,JS引擎总要做一番准备工作,这份工作其实就是创建对应的执行上下文;
function f1() {console.log('anlan');};f1();function f1() {console.log('noob');};f1();
这说明代码在执行前一定发生了某些微妙的变化,我们都知道它叫变量提升。那JS引擎究竟做了什么呢
执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文
全局执行上下文只有一个,在客户端中一般由浏览器创建,也就是我们熟知的window对象,我们能通过this直接访问到它。
全局对象window上预定义了大量的方法和属性,我们在全局环境的任意处都能直接访问这些属性方法,同时window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问。
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
eval函数执行上下文:执行在eval函数内部的函数也有自己的函数上下文
执行上下文栈也叫调用栈,当js解释器运行的时候,执行栈就会用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。U型
JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。
function f1() {f2();console.log(1);};function f2() {f3();console.log(2);};function f3() {console.log(3);};f1()
f1入栈 ---> f2入栈 ----> f3入栈 -----> f3 出栈 -----> f2 出栈 -----> f1 出栈
那么我们现在已经在一定程度上了解了上下文,那当一个函数创建的时候究竟发生了什么呢?
1. JavaScript创建一个新的函数执行上下文
2. 这个执行上下文将有它自己的一组变量,这些变量将是这个执行上下文的本地变量。
3. 新的执行上下文被推到到执行堆栈中
4. 直到遇到return语句或一个结束括号},函数执行结束
当函数结束时:
1. 弹出执行栈
2.函数将返回值返回调用上下文
3.这个本地函数执行上下文被销毁
闭包中最简单的例子
例1 :
let a = 3;let b = addTwo(a);console.log(b);function addTwo(x) {let ret = x + 2;return ret;};
我们在函数执行上下文中有变量,在全局执行上下文中有变量。JavaScript的一个复杂之处在于它如何查找变量,沿着作用域链查找而不是严格按照上下文的方式查找。
真正的闭包
function createCounter() {
let counter = 0
const myFunction = function() {
counter = counter + 1
return counter
}
return myFunction
}
const increment = createCounter()
const c1 = increment();const c2 = increment();const c3 = increment();
console.log('result', c1, c2, c3)
const myFunction = function()在这个函数创建的时候会把所有变量放进myFunction这个函数中闭包背包中去
const c2 = increment();再去执行这个函数的时候,值变成了2,原先应该被销毁,但是因为闭包,把值存进去啦
怎么看背包,打断点,然后作用,闭包作用域,如上图所述
无论何时声明新函数并将其赋值给变量,都要存储函数定义和闭包。闭包包含在函数创建时作用域中的所有变量,它类似于背包。函数定义附带一个小背包,它的包中存储了函数定义创建时作用域中的所有变量。
在全局作用域中创建的函数创建闭包,但是由于这些函数是在全局作用域中创建的,所以它们可以访问全局作用域中的所有变量,闭包的概念并不重要。
当函数返回函数时,闭包的概念就变得更加重要了。返回的函数可以访问不属于全局作用域的变量,但它们仅存在于其闭包中。
闭包的方法是通过背包的类比。当一个函数被创建并传递或从另一个函数返回时,它会携带一个背包。背包中是函数声明时作用域内的所有变量。
注意事项:容易导致内存泄漏。闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。
MDN对闭包的官方解释:
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
闭包是绑定在一起(封闭)的函数及其周围状态(词汇环境)的引用的组合。换句话说,闭包允许您从内部函数访问外部函数的scope(作用域)。在JavaScript中,每次创建函数时,都会在函数创建时创建闭包。
闭包绝不可以理解为在函数里返回函数的函数。这是引发闭包问题的原因,而不是闭包概念的本身
什么情况下会产生闭包现象?