这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战。
js篇之闭包
如果面试的时候问你闭包,你瞬间能想到的是什么?
如果你能想到什么是子函数可以用父函数的变量,基本上已经凉凉了。
如果你想到的是以下几个词:
- AO、VO、垃圾回收、内存泄漏。这些词的话估计能过关的几率还会大点。
当问到闭包的时候,要在脑子里梳理出来一条关于闭包的面试题流程,然后等着让面试官问(套路):
- 闭包=>AO、VO=>ES6变量的暂时性死区=>垃圾回收、内存泄漏等等
闭包科普
1.1. 执行上下文(excution context)
执行环境定义了变量或函数有权访问的其他数据,并决定其各自的行为。每一个执行环境都有一个对应的变量对象(VO) ,这个对象的作用就是保存在环境中定义的变量和函数(这个变量对象我们无法直接使用)
1.2. 全局执行环境
在JavaScript中window对象表示的是全局执行环境,全局执行环境是最外围的环境,所有的全局变量和函数都是作为window对象的属性和方法。这个执行环境在浏览器关闭时才进行销毁,也就是说window执行环境的生命周期贯穿整个浏览器显示
1.3. 局部执行环境
并不是只有window执行环境,每一个函数都有自己的执行环境,这些执行环境中所有代码执行完后,该环境被销毁。我们都知道,js中程序是以流形式执行的,当执行流执行到一个函数的时候,这个函数的执行环境会被推送到一个环境栈中,在函数执行完毕后,栈中的这个执行环境会被弹出并把控制权交给之前调用函数的执行环境。
1.4. 环境栈
JavaScript引擎在执行代码之前, 首先会创建一个执行环境栈. 然后创建全局环境并将它压入栈中作为栈底. 再之后 , 随着执行流进入函数中,函数的执行上下文便会被推入执行环境栈中( 执行环境栈始终从栈顶开始执行 ) 而在函数执行完毕后, 执行环境栈便会把这个函数的执行环境弹出 , 并将控制权返回给之前的执行环境.
1.5. 作用域
任何编程语言都有作用域的概念,简单来说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。
1.5.1. ES5没有块级作用域会带来以下问题:
- 变量提升导致内层变量可能会覆盖外层变量
- 用来计数的循环变量泄露为全局变量
1.5.2. ES6有块级作用域
- 块级作用域的出现使得立即执行函数不再需要。
- 块级作用域与函数声明
1.6. 作用域链(scope chain)
一般情况下变量取值会到创建这个变量函数的作用域中取值。 但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
每个执行上下文都有3个重要属性:
- 变量对象
- 作用域链
- this
1.6.1. 变量对象(VO):
每个执行环境都有一个与之相关联的对象,并且执行环境中定义的变量和函数都在其中,不能直接访问该对象。
由此我们便知道VO中存储了在执行环境中定义的变量和函数声明. 那我们所使用的变量或者函数都是在VO上来查询其是否可用,通常情况下,一个VO对象有以下信息:
- 变量
- 函数声明
- 函数的形参
1.6.1.1. 全局变量对象
对于全局环境,浏览器中用window对象来作为它的VO. 该全局对象中存储着JS所定义的全部内置对象和全局函数,以及宿主环境的宿主对象和我们在全局环境中定义的函数和变量等.
16.1.2. 局部变量对象
未进入执行阶段之前,变量对象中的属性都不能访问。但是进入执行阶段之后,变量对象转变为了活动对象(AO) ,里面的属性都能被访问了,然后开始进行执行阶段的操作。所以活动对象实际就是变量对象在真正执行时的另一种形式。对于局部环境我们用AO来作为VO,还要说明一下AO在最开始的时候只有一个变量,即arguments对象(这个对象在全局环境上是不存在的).
1.6.2. 作用域链的作用
作用域链是一条变量对象的链,它和执行上下文有关,用于在处理标识符的时候进行变量查询.
函数上下文的作用域链在函数调用的时候创建出来, 它包含了活动动对象和该函数的内部 [[Scope]] 属性.
创建作用域链过程总结
- 将函数内部[scope]属性中的所有对象按顺序复制到作用域链中,完成作用域链的初始化.
- 将当前执行上下文的VO放入作用域链的顶端,彻底完成作用域链的创建,这样当前执行上下文的VO一直都是其作用域链的开头
2. 闭包的应用(点赞关注领取资料)
- 普通函数
- 闭包函数
- 循环中闭包
- 闭包中的this
- 闭包的内存泄漏