作用域链
在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用成为outer。
function bar() {
console.log(myName)
}
function foo() {
var myName = "极客邦"
bar()
}
var myName = "极客时间"
foo()
上述代码在寻找myName变量时,如果在当前的变量没有找到,那么js引擎会继续在outer所指向的执行上下文中查找。
bar 函数和 foo 函数的 outer 都是指向全局上下文的,这也就意味着如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。我们把这个查找的链条就称为作用域链。
词法作用域
词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:foo 函数作用域—>bar 函数作用域—>main 函数作用域—> 全局作用域。
词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。
块级作用域中变量查找
执行到bar中if需要查找test,首先是在bar的执行上下文中查找,但是因为bar函数的执行上下文中没有定义test变量,所以根据词法作用域的规则,下一步就在bar函数的外部作用域中查找,也就是全局作用域。
闭包
function foo() {
var myName = "极客时间"
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())
当执行到foo函数内部的return innerBar这行代码时调用栈的情况:
根据词法作用域规则,内部函数getName和setName总是可以访问它们的外部函数foo中的变量,所以当innerBar对象返回给全局变量bar时,虽然foo函数已经执行结束,但是getName和setName函数依然可以使用foo函数中的变量myName和test1。所以当foo函数执行完成后,整个调用栈状态如下图:
可以看到,foo执行完后,其执行上下文从栈顶弹出了,但是由于返回的setName和getName方法中用了foo函数的变量,所以这两个变量依旧保存在内存中。这像极了这两个方法的一个专属背包,专属的原因是因为除了这两个方法之外,其他任何地方都是无法访问该背包的,所以我们就可以把这个背包称为foo函数的闭包。
可以在开发者工具”来看看闭包的情况,打开 Chrome 的“开发者工具”,在 bar 函数任意地方打上断点,然后刷新页面
闭包回收
- 通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。
- 如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
- 使用原则:若闭环需要持续使用,则可以作为全局变量存在,若使用频率不高,占内存较大的话,尽量以局部变量存在。