目录
背景
最近汉得面试官问了闭包问题,想要深入理解闭包,所以需要回头复习。
作用域
参考文章:https://www.cnblogs.com/ukerxi/p/8027236.html
官方解释是:“一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
function outer(){
// 声明变量
var name = "ukerxi";
// 定义内部函数
function inner() {
console.log(name); // 可以访问到 name 变量
}
inner()
}
console.log(name); // 报错,undefined
name在函数outer内定义了,所以在函数outer中,在name定义了之后的outer中的代码都可以访问到name,而outer函数外不可访问该name值。outer函数就是name的作用域
执行环境
对于执行环境,《javascript高级程序设计》讲到:执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
以我的理解,其实就是执行环境决定该环境干嘛
以上面代码为例:outer函数是定义在全局上,而inner函数时定义在outer内,每个函数都会有自己的执行环境,所以在栈中,windo环境是最低部的,当代码运行到outer函数时,outer环境会被推入到栈中,这时候var name = 'ukerxi'和声明inner函数这个行为就是在outer环境中执行的,也就是说outer环境要干嘛(变量声明和函数声明并且执行函数咯,这就是outer函数该干的),当执行到inner()这段代码是,因为inner是个函数,所以会有自己的环境,inner环境就会被推入环境栈中,在inner执行环境下,会打印name值(为什么可以打印name值,看下一节作用域链),inner函数代码执行完毕后,inner环境从环境栈中弹出,控制权给到outer函数,outer函数执行完毕,也从环境栈中弹出,以此类推,只要环境中代码执行完毕该环境就会从环境栈弹出。。
变量对象
每一个执行环境都会有一个与之关联的变量对象,环境中保存的所有变量和函数都保存在这个变量对象中,我们无法访问,但是解析器处理数据时会在后台用上。
作用域链
上一节的执行环境中说到:在inner执行环境下,会打印name值,这其实是因为作用域链的原因。
作用域链:保证对执行环境有权访问的所有变量和函数的有序访问。代码在环境中执行时,会创建变量对象的一个作用域链。,也就是每个环境都会有自己的作用域链
window环境:变量对象作用域链只有outer函数
outer环境:变量对象作用域链有windows的变量对象和outer本身的变量对象
inner环境:变量对象作用域链有windows的变量对象和outer的变量对象和inner的变量对象
变量对象作用域链有windows的变量对象和outer本身的作用域链
为什么inner函数可以打印name值,是因为在inner环境中,作用域链为inner变量对象->outer变量对象->window变量对象
console.log是在Inner环境中执行,他会先在inner变量对象中查找是否有name值,如果没有,则去outer变量对象找变量name,找到了name就打印了,找不到则继续沿着作用域链查找,直到找到或者找到window变量对象
outer环境的作用域链只有outer变量对象->window变量对象
闭包
网上很多观点,我个人觉得很多是理解错的,我说一下我的见解,如果不懂,可以先看完上面章节。
在《javascript高级程序设计》中讲到:闭包是指有权访问另一个函数作用域中的变量的集合
而在《前端面试指南》中讲到:函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的
变量,函数 B 就被称为闭包。
//情况1.这是闭包
function A() {
let a = 9;
function B() {
console.log(a)
}
return B
}
let test = A()
test()
//情况2.这不是闭包,因为函数B执行完毕后就立即销毁了B的作用域链了。
function A() {
let a = 9;
function B() {
console.log(a)
}
B()
}
A()
需要懂得情况2:执行栈,函数A执行时,执行环境A被推入环境栈中,该环境的行为是:声明变量a=9,声明函数B,然后执行函数B,执行函数B后,执行环境B会被推入环境栈中,执行环境B的行为是打印a,执行环境B的活动对象没有a,往作用域链的下一个活动对象(也就是执行环境A的活动对象)查找a,查找到后打印。然后函数B执行完毕,执行环境B弹出栈,函数A执行完毕,执行环境A弹出栈,然后保存在函数A的活动对象中的变量a=9和函数B都会被销毁。
那情况1呢?
情况1中;函数A执行后,执行环境A被推入环境栈中,该环境的行为是:声明变量a=9,声明函数B,然后返回函数B,函数B被返回之后,B的作用域链会初始化为包含A函数的活动对象(变量对象,该变量保存有a=9)和全局变量对象,函数B可以访问到a,A执行完毕后,会从环境栈中弹出,作用域链被销毁,但是他的活动对象(变量对象)不会销毁,因为函数B仍然引用这个活动对象,因为本质上作用域链是指向变量对象的指针列表,也就是说B的作用域链:B的变量对象->A的变量对象->全局变量对象,B还在引用A的变量对象,B告诉A,我还在用着这个变量对象,你不要销毁这个变量对象,所以A的变量对象(保存着A)一只保存在内存中,直到这个函数B被销毁不再引用A的变量对象,
//情况1.这是闭包
function A() {
let a = 9;
function B() {
console.log(a)
}
return B
}
let test = A()
test()
//调用完函数B之后,test指针改变,解除引用,从而释放内存
test =null
结论
简单来说:
定义:函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的
变量,函数 B 就被称为闭包
解决闭包内存占用:
不再引用返回的函数B,让垃圾回收机制回收掉B的作用域链。
适用场景:封装变量。