作用域链
- 在每个执行上下文的变量环境中,都包含了一个外部引用(outer),专门用来指向另外的执行上下文,当一段可执行代码使用了一个变量,JS引擎会去执行上下文中查找该变量:
- 首先会在当前的执行上下文中寻找该变量
- 如果找不到就会去 outer 指向的执行上下文中去寻找该变量,如果仍然还是找不到就会抛出错误
我们把这个像链条一样的查找的顺序称为作用域链,要想了解作用域链,首先得先了解词法作用域,因为作用域链是由词法作用域来决定的
词法作用域
- 词法作用域就是指 作用域是由代码中函数声明的位置来决定的,即在写好代码的那一刻,作用域就已经去确定好了,即词法作用域是静态的作用域,通过它就能来观测作用域链是怎样的
以下面一段代码作为示例
function baz(){
console.log(b)
}
function foo(){
let a = 2;
function bar(){
let b = 3;
console.log(a) // 2
baz() // undifined
}
bar();
console.log(b) // undifined
}
foo()
从这段代码可以很容易地看出,作用域以及作用域链是怎么样的。我们从执行上下文的角度来分析,如图
通过上面我们可以清楚地发现JS的作用域链就是由词法作用域来决定的,即是由代码的物理位置决定的
闭包
- JS中,根据词法作用域的规则,内部函数总是可以访问其所在的外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,那么这些变量的集合就称为那个外部函数的闭包,比如外部函数为foo,则称为foo函数的闭包
通过实际代码来理解
function foo()
{
var name = 'abc'
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return name
},
setName:function(newname){
name = newname
}
}
return innerBar
}
var bar = foo()
bar.setName('123')
bar.getName() // 1
console.log(bar.getName()) // 1 123
console.log(test1) // undifined
这里不难发现 明明 foo 函数已经执行完了,执行栈中的foo函数的执行上下文也被弹出了,但在调用 bar.getName() 打印 test1 的时候,仍然打印了 test1 的值,说明 test1 并没有被销毁,但在全局直接打印 test1 的时候,结果却是 undifined,说明 test1 只能够被 bar.getName() 所调用
下面从执行上下文的角度来分析闭包出现的时候的内存情况
getName会按照自己的执行上下文、foo闭包、全局执行上下文的作用域链来寻找变量
通过 Chrome devtool ,调试的时候打开 scope 也可以查看闭包,以及当前函数的作用域链
闭包的回收
通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭,容易造成内存泄漏,但当把引用该闭包的变量的引用修改成null,JS引擎的垃圾回收器就会回收这块内存
如果引用闭包的函数是个局部变量,等函数销毁后,在下次JS引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用,那么JS引擎的垃圾回收器就会回收这块内存