作用域链

127 阅读5分钟

什么是作用域?

作用域是指程序源代码中存储变量的区域。

程序需要一套设计良好的规则来存储变量,并且以后可以方便的找到这些变量,这套规则被称为作用域。

JS是词法作用域

作用域的规则主要有两种:词法作用域动态作用域,js采用的是词法作用域,也就是说变量的查找规则是书写代码时就已经确定的。动态作用域是变量查找规则由函数执行时的位置决定的。

词法作用域是你找变量时,从函数定义(书写)的地方,向外层代码块找。动态作用域是,你从函数执行处,往外层代码块找。

JS引擎执行逻辑

JS引擎处理代码时,是一块一块的处理,而不是逐行处理。而且是分为两个阶段去处理,先编译、再执行

编译:

在编译阶段,会为该代码块中的变量声明分配内存,并命名。例如在处理var a=2;这句代码时,编译阶段会在该作用域声明一个变量,并命名为a,但此时a的值为undefiend。

编译阶段会对函数形参、arguments对象、变量声明、函数声明进行分配内存的处理。变量声明不会覆盖之前的同名声明,但是函数声明会覆盖之前的同名声明普通变量的初始值是undefiend,函数参数有传值时初始值为传值,无传值时为undefiend,函数声明的初始值是该函数(函数在此时被创建)。

执行:

例如处理var a=2;时,js引擎会在作用域中找到a,然后执行a = 2这条命令,给a赋值。

执行上下文

执行上下文,也称执行环境,实际上也是一个对象。执行上下文栈,用来管理执行上下文的栈(也称函数调用栈、执行环境栈)。

执行上下文分为三种:全局执行上下文、函数执行上下文、Eval执行上下文(在执行阶段,Eval可以将传入的字符串转换为代码去执行,而这个传入的字符串可以是一个变量)。

每个函数都有自己的执行环境,当程序调用一个函数时,该函数的执行环境被压入环境栈中,当函数执行完毕时,该函数的执行环境会出栈,并被销毁。栈底是全局执行环境。

执行上下文三剑客(三个重要属性)

函数被调用的时候,会创建对应的函数上下文,并推入执行上下文栈中。执行上下文创建时,会生成三个重要的属性:变量对象、作用域链、this指向

变量对象:

函数中定义的变量、参数、函数、arguments对象都存储在这个变量中,初始值只包含arguments对象。

我们无法使用js代码访问变量对象,但js引擎可以在后台使用它。函数进入执行阶段时,原本不能访问的变量对象被激活成为一个活动对象,自此,我们可以访问到其中的各种属性。其实变量对象和活动对象是一个东西,只不过处于不同的状态和阶段而已

全局上下文中的变量对象就是全局对象,以浏览器环境来说,就是 window 对象。

注:官方说法是每一个执行环境都有一个与之关联的变量对象。那么如何关联?个人理解既然执行环境本身也是对象,关联无非是用属性存储一个对变量对象引用的指针。这才会有闭包情况下,执行环境被销毁,但关联的变量对象可能依旧被其他作用域链引用,依旧存在于内存中,不会被回收。

作用域链:

执行环境中存储着该环境的作用域链,作用域链本质上是一个指向变量对象的指针列表,它只引用但不包含实际变量对象。函数在被创建时,会根据词法作用域,将作用域链保存在内部的[[Scope]]属性中,当函数被调用时,执行环境入栈后,会将该函数的变量对象放到作用域链的最前面,形成最终的作用域链存储到执行环境中。

this指向:

函数只有在调用的时候,才知道内部的this到底指向的谁,因此this的指向实际是在执行上下文中保存着。具体内容在this指向的文章中有说明。

总结

执行环境的创建和入栈过程,实际就是我们上面所说的编译阶段,然后进入函数执行阶段,然后是执行环境出栈、销毁,回收相关内存。一般来讲函数执行完毕,执行环境出栈,该环境会被销毁,关联的变量也会被销毁。但是闭包情况下又有所不同,准确的说是函数的执行环境及作用域链被销毁,但变量对象仍存在于内存中(被其他作用域链引用)。