作用域链和闭包
上个博客了解了如何通过词法环境和变量环境来查找变量,这其中就涉及到作用域链了,抽象的概念不如来段代码,来,上板栗🌰。
作用域链
1 | function printName() { |
那么main函数执行的结果是什么呢?当执行到printName函数内部时,name的值到底到底是从全局执行上下文还是main函数的执行上下文中取呢?可能容易按照调用栈的顺序来查找变量,执行到printName函数内部时,从栈顶到底依次为printName函数执行上下文、main函数执行上下文、全局执行上下文,如果是这样输出结果应该是able,但实际上不是这样,这是为甚呢,让我们进入作用域链的世界探究探究。
在每个执行上下文的变量环境里都有一个外部引用,指向外部的执行上下文,可称其为outer。一段代码中使用一个变量时,js引擎会先在当前执行上下文中查找该变量,然后会继续在outer指向的执行上下文中查找。printName函数和main函数的outer都是指向全局上下文,那么上面的栗子在printName函数中要输出name变量,则会取outer指向的全局作用域中寻找,这种查找方式即为链。
可能看到这里还会有一些疑问,printName函数是main函数调用的,为啥其outer不是指向main函数呢。这里涉及到一个概念词法作用域,在js的执行过程中,作用域链是由词法作用域来决定的。词法作用域呢表示作用域是由代码中函数声明的位置来决定的,所以说它是静态的,是在代码阶段就定下来了,跟函数是怎么互相调用的没有关系。
闭包
为了更好的理解闭包,再举个板栗🌰。
1 | function foo() { |
innerFun是一个对象,里边包含两个方法,这两个方法中用到了a变量和name变量。根据词法作用域,内部的函数可以访问外部函数foo中的变量。所以当innerFun返回给全局变量funA时,它的两个方法仍可以使用foo函数中的变量。
foo函数执行完后,其执行上下文从栈顶弹出,但是其返回的两个方法中使用了变量name和a,因此这两个变量仍保存在内存中。我们可以把它想象成setName和getName两个方法的背包,无法在哪里调用这两个方法,它们都会背着这个foo函数的背包,我们可以把这个背包称为foo函数的闭包。
此时此刻,上一个比较规范的闭包定义吧。在js中,根据词法作用域的规则,内部函数可以访问外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束,但内部函数引用外部函数但变量依然保存在内存中,我们称这些变量的集合称之为外部函数的闭包。
那么当执行到funA.setName方法中的name = ‘moriaty’时,js引擎会沿着“当前执行上下文->foo函数闭包->全局执行上下文”的顺序来查找name变量,可以看下此时的调用栈如下图。所以调用setName时,会修改闭包中name的值。
关于闭包的销毁,如果引用闭包的函数是一个全局变量,那闭包会一直存在到页面关闭,但是如果这个闭包之后不实用的话,会造成内存泄漏。如果引用闭包的函数是个局部变量,等函数销毁后,下次js引擎执行垃圾回收时,判断闭包不再被使用,那么内存会被回收。