JavaScript 中的作用域链、上下文和变量对象是非常重要的概念,它们是实现变量和函数作用域和访问控制的关键组成部分。
作用域链(Scope Chain)
作用域链是一种链式结构。用于解析变量和函数的作用域。在 JavaScript 中,每个函数都有自己的作用域,作用域指的是在程序中访问变量和函数的可见性和可访问性。作用域链由当前函数的变量对象和所有外部函数的作用域链组成,它决定了在当前执行上下文中访问变量和函数时的优先级和范围。
上下文(Context)
上下文是 JavaScript 中执行代码的环境,它是一个存储有关当前执行环境的信息的内部数据结构,包括函数调用堆栈、当前执行的代码以及this关键字等信息。每当执行一个函数时,都会创建一个新的执行上下文对象,并将其添加到调用堆栈的顶部。
变量对象(Variable Object)
变量对象是每个执行上下文的内部对象,用于存储该执行上下文中定义的所有变量和函数声明。变量对象的主要功能是存储,其存储内容包括变量声明、函数声明和形参。变量对象是作为该执行上下文的属性创建的,也就是说它会在函数执行的那一刻同上下文一起被创造。当函数执行完成后,变量对象被销毁,其内部的变量和函数声明也随之消失。
作用域链如何工作?
作用域链是由当前执行上下文的变量对象和所有外部函数的作用域链组成的。当查找变量或函数时,JavaScript 引擎会沿着作用域链向上搜索,直到找到第一个匹配的标识符为止。在查找变量或函数时,JavaScript 引擎会先查找当前执行上下文的变量对象,如果没有找到匹配的标识符,则会沿着作用域链向上搜索。如果在最顶层的变量对象中都没有找到匹配的标识符,则会抛出一个 ReferenceError 异常。因此,作用域链的存在使得 JavaScript 中的变量和函数具有了访问控制的功能,同时也避免了变量名的冲突问题。
上下文如何工作?
在 JavaScript 中,每当执行一个函数时,都会创建一个新的执行上下文对象,并将其添加到调用堆栈的顶部。当函数执行结束后,执行上下文对象会被弹出堆栈,同时变量对象和作用域链也随之销毁。这个过程可以看作是一个栈的结构,称之为“执行上下文栈”。
值得注意的是,全局上下文是在 JavaScript 代码被加载时创建的,它在整个代码执行期间都存在。
同时,JavaScript 引擎在执行代码时,会根据当前执行环境的不同,创建不同类型的上下文对象。常见的上下文对象包括全局上下文、函数上下文、eval 上下文等。
变量对象如何工作?
变量对象被称为“活动对象”(Activation Object),它是一个特殊的对象,它在函数执行期间被用来存储函数内部的变量和函数声明,并且会被添加到作用域链的顶部,以供变量和函数访问和操作。变量对象(Variable Object)是在上下文创建时创建的内部对象,也就是说变量对象是上下文环境的子集。
一幢大楼中函数的一生
我们可以将一个函数在创建、执行、执行结束后弹出堆栈的过程比喻成一幢大楼中进出房间的过程,进入房间需要使用门禁卡(上下文),每个房间都有一个名称牌(变量对象)和门牌号(作用域链)。
**当函数被创建时,相当于在大楼中建造了一间新的房间(建立上下文)。**这个房间的名称牌(变量对象)上会写有房间内部定义的所有变量和函数声明的名称和值。同时,这个房间的门牌号(作用域链)也会被确定,它连接了当前房间和外部房间的门牌号,决定了变量和函数在该房间内部的可见性和可访问性。
**当函数被调用时,就相当于进入了这个新建的房间(进入上下文)。**此时,我们需要使用门禁卡(上下文)来进入房间,并且会将当前房间的门牌号(作用域链)加入到大楼门牌号列表的最前面,形成一个新的门牌号列表。
**在进入房间后,我们可以看到房间中的所有东西,包括名称牌(变量对象)上的所有变量和函数声明。**当我们需要访问一个变量或函数时,JavaScript 引擎会先查找当前房间的名称牌(变量对象),如果找不到,则沿着门牌号列表(作用域链)向上查找,直到找到匹配的变量或函数为止。
**当函数执行结束时,就相当于离开了这个房间。**此时,我们需要使用门禁卡(上下文)离开房间,并将当前房间的门牌号(作用域链)从门牌号列表的最前面弹出,形成一个新的门牌号列表。同时,当前房间的名称牌(变量对象)也会被销毁,其中的所有变量和函数声明也随之消失。
总之,作用域链、上下文和变量对象三者共同决定了变量和函数在 JavaScript 中的访问和执行规则。
本文用于记录自己的学习,欢迎大家指出错误。