作用域和作用域链
在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。
全局变量
变量在函数外定义,即为全局变量。
全局变量有 全局作用域: 网页中所有脚本和函数均可使用。
在javascript中的全局作用域其实就是window(Global Object)
优点:可重复使用,随处可用
缺点:会造成全局污染
局部作用域
变量在函数内声明,变量为局部作用域。
局部变量:只能在函数内部访问。
临时创建的活动对象AO(Activation Object)、该对象包含了函数的所有局部变量、命名参数、参数集合以及this,当运行时上下文被销毁、活动也会被销毁
优点:不污染全局
缺点:不可重复使用、仅在函数内可以使用
作用域链
一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。
但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链
闭包
有权访问另一个函数作用域中的变量的函数;一般情况就是在一个函数中包含另一个函数。
特点:
- 闭包是一个函数,而且存在于另一个函数当中
- 闭包可以访问到父级函数的变量,且该变量不会销毁
闭包的原理
闭包的实现原理,其实是利用了作用域链的特性
通过一段闭包的代码,我们又这段代码讲讲闭包的原理
function addAge(){
var age = 21;
return function(){
age++;
console.log(age);
}
}
var clourse = addAge();
clourse();
clourse();
clourse();
image.png第一阶段:在内存中创建执行执行环境栈、把全局对象window压入栈底、在window中声明变量
image.png第二阶段:
1、在栈中添加addAge的函数调用
2、为addAge函数创建活动对象AO、根据addAge函数的scope可以知道其活动对象指向window
3、window对象中的clourse变量记录着addAge()返回的匿名函数的地址[现在addAge()和clourse变量都可以找到匿名函数和addAge()产生的AO]
image.png第三阶段:addAge()调用完毕出栈、其对活动对象AO的引用也随之消失。
由于匿名函数中的scope引用着活动对象AO、匿名函数的地址也被clourse变量记录着。因此,addAge()虽然出栈了,对它的活动对象的引用也消失了,但是其活动对象被匿名函数的scope拽着、所以无法释放不会被回收。
大家观看蓝色的箭头,其实可以发现、蓝色的箭头已经形成了一个闭环了。
此时,由图也可以看出,活动对象AO只能通过clourse变量来找到。这里形成了一个闭包。保存了addAge()函数中的局部变量,使其可以重复使用,但是又不会造成全局污染。这就是闭包的一个使用场景:保存现场。
至于怎么调用重复使用局部变量,具体过程请看下面两幅图。
image.png第四阶段:
clourse()进栈,产生clouse()的活动对象AO,根据它的scope可以知道它的parent指向addAge()产生的活动对象AO。
clouse()执行age++,由于在它自己的作用域里面没有age、于是它会到上一级作用域查找age,它在它的上一级作用域中找到了age,于是对其进行了age++,age从21变成了22。执行console.log(age)输出22。
image.png第五阶段:
clourse()出栈,因为clourse产生的AO没有scope拽着它,因此clourse的AO是可以正常释放的。函数出栈,其AO被JS的垃圾回收机制回收。
clourse变量中的匿名函数中的scope依旧拽着addAge()产生的活动对象AO,于是这个活动对象依旧无法被释放[而且这个AO现在只能被clourse找到、clourse可以重复使用这个AO里面的局部变量age、又不会造成全局污染]
闭包就像一层保护膜,只要引用的函数一直存在,闭包就保护该作用域内的变量不被垃圾回收。针对在函数声明那一时间点的作用域内的所有函数和变量,闭包创建了一个“安全气泡”,因此函数获得了执行操作所需要的所有东西,包含了函数及其变量,和函数本身在一起。
网友评论