作用域链
每一个执行上下文的变量环境中都存在一个outer指针,用来指向外部的执行上下文
当v8在查找一个变量时,会在当前执行上下文的变量环境中查找,如果没有,就去outer指针指向的执行上下文的变量环境中查找,直到找到,或者outer指针指向全局执行上下文(全局执行上下文的外部执行上下文是null)我们把这个查找的链条称作作用域链
function bar(){
console.log(myName)//李四
}
function foo(){
var myName = '张三'
bar()
}
var myName = '李四'
foo()
当我们进行预编译,大概情况应该是这样的
但是当我们执行代码时,因为在bar函数中找不到myName,所以v8引擎会往上一级执行上下文中寻找,所以会来到全局执行上下文而不是foo执行上下文,所以最后输出的结果应该是
李四
闭包
知道上面那个概念之后,闭包便很好理解了
闭包的出现源自下面两种规则:
- 一般情况下当一个执行上下文被执行完毕之后,便会被销毁
- 根据作用域的查找规则,内部函数一定可以访问外部函数中的变量
这两种情况被结合的后果就是:当一个函数被销毁的时候,但是它的子函数需要用到这些被销毁的变量时,就会被保存起来,但是依旧放在栈中,这些变量的集合就叫做闭包
你可能觉得我在叽里咕噜说些什么呢,没关系,我们找个简单点的例题来看一下
let count = 1
function main(){
let count = 2
function bar(){
let count = 3
function foo(){
console.log(count)//3
}
foo()
}
bar()
}
main()
首先,对这段函数进行简单的预编译试试看
当v8执行代码时,会从全局开始执行,一步步执行到foo的执行上下文中,但是因为有规则一的情况,bar的执行上下文和main的执行上下文应该被删除,这就导致foo在执行时,outer指针指不到foo的父级执行上下文bar中,那么就应该报错,但是因为规则二的情况,bar执行上下文就算被销毁,也会留下foo所需要的函数,放在闭包中,所以最后输出的应该是3
众所周知,v8执行上下文的时候,会把执行上下文销毁,是为了给栈省空间,但是闭包的存在又让空间变得没法那么省,就会带来不好的情况,也就是爆栈,这就是闭包的缺点
那既然闭包有缺点,那么阴阳平衡,肯定就会有优点的,那就是可以定义私有模块,防止全局变量污染
比如:
let count = 0
function add() {
count++
return count
}
console.log(add());//1
console.log(add());//2
console.log(add());//3
这个时候,明明只是为了一个计数器,却在全局定义了一个count,万一下次要用定义count时,就会出问题,造成全局变量污染
这时候我们就要用闭包的特性,做一个计数器:
function bar() {
let a=0
return function () {
a++
return a
}
}
var res=bar()
console.log(res())//1
console.log(res())//2
console.log(res())//3
这时候,当函数被执行完毕之时,变量a就会被销毁,防止全局变量污染