什么?你还搞不清楚作用域链和闭包的关系吗

178 阅读7分钟

js在执行代码时到底做了什么?妈妈再也不用担心我js基础了

词法环境,执行上下文,执行环境,作用域,作业域链,闭包,变量对象,活动对象,this指向,调用栈,变量提升

想必大家都会对这些名词感到熟悉又陌生吧,本篇文章将会从js引擎如何执行js代码这一过程带你真正理解这些名词!

首先在js代码运行时会有以下几个阶段

1.语法分析阶段

在语法分析阶段主要是对代码进行语法分析,检查是否有语法错误,如果发现语法错误,就会在控制台抛出异常并终止执行,其中语法分析阶段属于编译器通用内容,像执行环境,词法环境,作用域,执行上下文都是在编译和执行收纳

2.编译阶段

而编译编译阶段会进行执行上下文的创建,执行上下文的创建包括了创建变量对象,建立作用域链,确定this的指向这3个方面

3.执行阶段

执行阶段主要是创建执行上下文压入调用栈,并成为正在运行的执行上下文,代码结束后,将其弹出调用栈

js代码运行各个阶段 (1)

知道了js!代码运行的这3个阶段,那让我们再更加深入的理解编译阶段里面的内容,其中编译阶段最主要做的就是执行上下文的创建

1.创建变量对象

首先什么是变量对象?

在每个执行上下文中都会有一个关联对象,该对象上会保存这个上下文中定义的所有变量和函数,例如:在浏览器中,全局环境的变量对象是window对象,所有的全局变量和函数都是作为window对象和属性和方法创建的。

知道了什么是变量对象,那js又是怎么创建变量对象的呢?

创建变量对象会创建argument对象,同时会检查当前上下文的函数声明和变量声明。

🍔其中变量声明就是会给变量分配内存,并将其初始化为undefind(该过程只会定义声明,执行阶段才会执行赋值语句,将其赋值),这便也是我们常说的变量提升

🍔而函数声明就是会在内存里创建函数对象,并且直接初始化为该函数对象,这便也是我们所说的函数声明

其中函数提升优先于变量提升,因为变量提升容易带来变量在预期内被覆盖掉的问题,同时还可能导致本该被销毁的变量没有被销毁等情况,因此es6中引入了let和const关键词,从而使js也拥有了块级作用域。

那这里我们就衍生出一个问题作用域是什么?js的作用域的体系是什么?

作用域是据名称来查找变量的一套规则,可以把作用域通俗理解为一个封闭的空间,这个空间是封闭的,不会对外部产生影响,外部空间不能访问内部空间,但是内部空间可以访问将其包裹在内的外部空间。

到这里或许你会对词法作用域,作用域,执行上下文,词法环境关系依旧感到慌乱,没关系,我们现在就来疏通一下他们的关系!

词法作用域中的变量,在编译过程中会产生一个确定的作用域,这个作用域即是当前的执行上下文,在es5以后我们使用词法环境来代替作用域来描述该执行上下文

词法环境 = 作用域 = 当前的执行上下文

其中词法环境又分为变量环境词法环境,分别来记录不同的变量声明,也就是说创建变量过程中会进行函数提升和变量提升,js会通过词法环境来记录函数声明变量声明,通过使用2个词法环境,而不是一个,分别记录不同的变量声明内容,因此js才能在实现支持块级作用域的同时,不影响原有的变量声明和函数声明,这就是创建变量的过程,他属于执行上下文中创建的一环

词法环境

而js作用域的体系可以用这张图来表示

js作用域体系

这里我就不再赘述了,以后可以单独写一篇文章来结束这些作用域的具体的含义

创建变量的过程会产生作用域,作用域也会被称为词法环境,那词法环境是由什么组成的呢?下面我们来结合作用域的创建过程来进行分析!

2.建立作用域链

建立作用域链就是将各个作用域链通过某种方式来连接在一起

其中作用域有两个成员组成,一个是环境记录,用于记录自身词法环境中的变量对象,另一个是外部词法环境引用,用来记录外层词法环境的引用

词法环境 (1)

通过外部词法环境的引用,作用域可以层层拓展,建立起从里到外延伸的一条作用域链

当某个变量无法在自身词法环境记录找到时,根据外部词法环境引用向外进行寻找,自到最外层的词法环境中外部词法环境引用为null, 这便是作用域链的变量查询

那这个外部词法环境引用又时怎怎样指向外层的呢?

在代码进入执行阶段之后,js会对变量进行赋值,此时变量对象会转换为活动对象(AO),转换后的活动对象才可以被访问,所以说活动对象和变量对象其实是一个东西,只不过所在的环境不同导致的变化。

同时在执行阶段时,是分为一个又一个的执行期组成的,然后js把这些执行期放在一个栈里面,我们通常把这个栈称作为调用栈,js通过调用栈的方式来执行js代码,当函数执行结束后,执行期伤上下文将被销毁(作用域链和对象均被销毁)但有时我们想保留其中的一些变量对象,不想被销毁,那此时我们就会用到闭包

闭包的原理就是通过作用域链,可以在函数内部可以直接读取外部以及全局变量但外部环境是无法访问内部函数里面的变量,形成闭包。

闭包我们一般是用来干啥的呢?

1.用于从外部读取其他函数内部变量的函数

2.可以使用闭包来模拟私有方法

3.让这些变量的值始终保持在内存中

在使用闭包的时候需要注意一点,如果你使用闭包的时候,需要及时清理不再使用到的变量,否则可能导致内存泄漏的问题

3.确定this的指向

this的指向问题一直都是让人很头疼的东西,但只要掌握好他的所有的情况this的指向问题,就在根据代码分析,其实this指向问题也不是个难题

这里我总结了所有情况的this指向问题

this指向问题

\