复习第一轮-作用域、作用域链和执行环境以及搞懂闭包。

目录

背景

作用域

执行环境

变量对象

作用域链

闭包


背景

最近汉得面试官问了闭包问题,想要深入理解闭包,所以需要回头复习。

 

作用域

参考文章: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的作用域链。

适用场景:封装变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值