JS编程建议——78:正确理解执行上下文和作用域链

简介: 78:正确理解执行上下文和作用域链

建议78:正确理解执行上下文和作用域链
执行上下文(execution context)是ECMAScript规范中用来描述 JavaScript 代码执行的抽象概念。所有的 JavaScript 代码都是在某个执行上下文中运行的。在当前执行上下文中调用 function会进入一个新的执行上下文。该function调用结束后会返回到原来的执行上下文中。如果function在调用过程中抛出异常,并且没有将其捕获,有可能从多个执行上下文中退出。在function调用过程中,也可能调用其他的function,从而进入新的执行上下文,由此形成一个执行上下文栈。
每个执行上下文都与一个作用域链(scope chain)关联起来。该作用域链用来在function执行时求出标识符(identifier)的值。该链中包含多个对象,在对标识符进行求值的过程中,会从链首的对象开始,然后依次查找后面的对象,直到在某个对象中找到与标识符名称相同的属性。在每个对象中进行属性查找时,会使用该对象的prototype链。在一个执行上下文中,与其关联的作用域链只会被with语句和catch 子句影响。
在进入一个新的执行上下文时,会按顺序执行下面的操作:
(1)创建激活(activation)对象
激活对象是在进入新的执行上下文时创建出来的,并且与新的执行上下文关联起来。在初始化构造函数时,该对象包含一个名为arguments的属性。激活对象在变量初始化时也会被用到。JavaScript代码不能直接访问该对象,但可以访问该对象的成员(如 arguments)。
(2)创建作用域链
接下来的操作是创建作用域链。每个 function 都有一个内部属性[[scope]],它的值是一个包含多个对象的链。该属性的具体值与 function 的创建方式和在代码中的位置有很大关系(见本建议后面介绍的“function 对象的创建方式”内容)。此时的主要操作是将上一步创建的激活对象添加到 function 的[[scope]]属性对应的链的前面。
(3)变量初始化
这一步对function中需要使用的变量进行初始化。初始化时使用的对象是创建激活对象过程中所创建的激活对象,不过此时称做变量对象。会被初始化的变量包括 function 调用时的实际参数、内部function和局部变量。在这一步中,对于局部变量,只是在变量对象中创建了同名的属性,其属性值为undefined,只有在 function 执行过程中才会被真正赋值。全局JavaScript代码是在全局执行上下文中运行的,该上下文的作用域链只包含一个全局对象。
函数总是在自己的上下文环境中运行,如读/写局部变量、函数参数,以及运行内部逻辑结构等。在创建上下文环境的过程中,JavaScript会遵循一定的运行规则,并按照代码顺序完成一系列操作。这个操作过程如下:
第1步,根据调用时传递的参数创建调用对象。
第2步,创建参数对象,存储参数变量。
第3步,创建对象属性,存储函数定义的局部变量。
第4步,把调用对象放在作用域链的头部,以便检索。
第5步,执行函数结构体内语句。
第6步,返回函数返回值。
针对上面的操作过程,下面进行详细描述。
首先,在函数上下文环境中创建一个调用对象。调用对象与上下文环境是两个不同的概念,也是另一种运行机制。对象可以定义和访问自己的属性或方法,不过这里的对象不是完整意义上的对象,它没有原型,并且不能够被引用,这与Arguments对象的arguments[]数组不是真正意义上的数组一样。
调用对象会根据传递的参数创建自己的Arguments对象,这是一个结构类似数组的对象,该对象内部存储着调用函数时所传递的参数。接着,创建名为arguments的属性,该属性引用刚创建的Arguments对象。
然后,为上下文环境分配作用域。作用域由对象列表或对象链组成。每个函数对象都有一个内部属性(scope),这个属性值也是由对象列表或对象链组成的。 scope属性值构成了函数调用上下文环境的作用域,同时,调用对象被添加到作用域链的头部,即该对象列表的顶部(作用域链的前端)。
实际上,这个头部是针对该函数的作用域链而言的,把调用对象添加到作用域的头部就是把调用对象排在函数作用域链的最上面。例如,在下面这个示例中,当调用函数e()时,将创建函数e()的调用对象和函数e()的作用域,但在调用函数e()之前,会先调用函数g(),并且生成调用函数g()的对象。而调用函数e()的对象会在函数e()的作用域范围内处于头部位置,即排在最前面。代码如下:
function f(){

return e();
function e(){
    return g();
    function g(){
        return 1;
    }
}

}
alert(f()); // 1
接着,正式执行函数体内代码,此时JavaScript会对函数体内创建的变量执行变量实例化操作(即转换为调用对象的属性)。下面进行具体说明。
将函数的形参也创建为调用对象的命名属性,如果调用函数时传递的参数与形参一致,则将相应参数的值赋给这些命名属性,否则会将命名属性赋值为undefined。
对于内部定义函数(注意其与嵌套函数的区分,两者语义不完全重合),会以其声明时所用名称为调用对象创建同名属性,对应的函数则被创建为函数对象,并将其赋值给该属性。
将在函数内部声明的所有局部变量创建为调用对象的命名属性。注意,在执行函数体内的代码并计算相应的赋值表达式之前不会对局部变量进行真正的实例化。
由于arguments属性与函数局部变量对应的命名属性都属于同一个调用对象,因此可以将arguments 作为函数的局部变量来看待。
最后,创建this对象并对其进行赋值。如果赋值为一个对象,则this将指向该对象引用。如果赋值为null,则this就指向全局对象。
创建全局上下文环境的过程与上面的描述稍微不同,因为全局上下文环境没有参数,所以不需要通过定义调用对象来引用这些参数。全局上下文环境会有一个作用域,即全局作用域,它的作用域链实际上只由一个对象组成,即全局对象(window)。全局上下文环境也会有变量实例化的过程,它的内部函数就是涉及大部分 JavaScript 代码的、常规的顶级函数声明。全局上下文环境也会使用this对象来引用全局对象。
JavaScript作用域可以细分为词法作用域和动态作用域。词法作用域又称为定义作用域,这是从静态角度来说的。在函数没有被调用之前,根据函数结构的嵌套关系来确定函数的作用域。因此词法作用域取决于源代码,通常编译器可以进行静态分析来确定每个标识符实际的引用。
动态作用域也称为执行作用域,这是从动态角度来说的。当函数被调用之后,其作用域会因为调用而发生变化,此时作用域链也会随之调整。
定义作用域就是用来说明函数在定义时存在的嵌套关系。当函数被执行时,作用域可能会发生变化。JavaScript函数运行在它们被定义的作用域中,而不是它们被执行的作用域中。
在 JavaScript 中,function 对象的创建方式有3种:function 声明、function 表达式和使用 Function 构造器。
function a() {}
var a = function() {}
var a = new Function()
通过这3种方法创建出来的 function 对象的scope属性的值有所不同,从而影响 function执行过程中的作用域链,具体说明如下:
使用function语句声明的function对象是在进入执行上下文时的变量初始化过程中创建的。该对象的scope属性的值是它被创建时的执行上下文对应的作用域链。
使用function表达式的function对象是在该表达式被执行的时候创建的。该对象的scope属性的值与使用function声明创建的对象一样。
使用Function构造器声明一个function通常有两种方式,常用格式是var funcName = new Function(p1, p2,..., pn, body),其中 p1,p2,…,pn 表示的是该function的形式参数,body是function的内容,使用该方式的function对象是在构造器被调用的时候创建的。该对象的scope属性的值总是一个只包含全局对象的作用域链。
function对象的length属性可以用来获取声明function时指定的形式参数的个数,而function对象被调用时的实际参数是通过arguments来获取的。

相关文章
|
4天前
|
JavaScript 前端开发
js的作用域作用域链
【10月更文挑战第29天】理解JavaScript的作用域和作用域链对于正确理解变量的访问和生命周期、避免变量命名冲突以及编写高质量的JavaScript代码都具有重要意义。在实际开发中,需要合理地利用作用域和作用域链来组织代码结构,提高代码的可读性和可维护性。
|
2月前
|
存储 JavaScript 前端开发
JavaScript编程实现tab选项卡切换的效果+1
JavaScript编程实现tab选项卡切换的效果+1
|
3月前
|
JavaScript 前端开发 编译器
解锁JavaScript模块化编程新纪元:从CommonJS的基石到ES Modules的飞跃,探索代码组织的艺术与科学
【8月更文挑战第27天】随着Web应用复杂度的提升,JavaScript模块化编程变得至关重要,它能有效降低代码耦合度并提高项目可维护性及扩展性。从CommonJS到ES Modules,模块化标准经历了显著的发展。CommonJS最初专为服务器端设计,通过`require()`同步加载模块。而ES Modules作为官方标准,支持异步加载,更适合浏览器环境,并且能够进行静态分析以优化性能。这两种标准各有特色,但ES Modules凭借其更广泛的跨平台兼容性和现代语法逐渐成为主流。这一演进不仅标志着JavaScript模块化的成熟,也反映了整个JavaScript生态系统的不断完善。
50 3
|
22天前
|
存储 JavaScript 前端开发
深入理解 JavaScript 执行上下文与 this 绑定机制
JavaScript 代码执行时,会为每段可执行代码创建对应的执行上下文,其中包含三个重要属性:变量对象、作用域链、和 this。本文深入剖析了执行上下文的生命周期以及 this 在不同情况下的指向规则。通过解析全局上下文和函数上下文中的 this,我们详细讲解了 this 的运行期绑定特性,并展示了如何通过调用方式影响 this 的绑定对象。同时,文中对箭头函数 this 的特殊性以及四条判断 this 绑定的规则进行了总结,帮助开发者更清晰地理解 JavaScript 中的 this 行为。
59 8
深入理解 JavaScript 执行上下文与 this 绑定机制
|
3天前
|
自然语言处理 JavaScript 前端开发
如何在 JavaScript 中创建执行上下文
在JavaScript中,每当执行一段代码时,都会创建一个执行上下文。它首先进行变量、函数声明的创建和内存分配(即变量环境和词法环境的建立),接着进入代码执行阶段,处理具体逻辑。
|
9天前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包:解锁编程潜能,释放你的创造力
【10月更文挑战第25天】本文深入探讨了JavaScript中的闭包,包括其基本概念、创建方法和实践应用。闭包允许函数访问其定义时的作用域链,常用于数据封装、函数柯里化和模块化编程。文章还提供了闭包的最佳实践,帮助读者更好地理解和使用这一强大特性。
11 2
|
3天前
|
存储 自然语言处理 JavaScript
如何在 JavaScript 中创建执行上下文
在JavaScript中,作用域链是一套用于查找变量和函数的机制,由当前执行上下文的变量对象和所有外层执行上下文的变量对象组成。它包括全局作用域、函数作用域和块级作用域。作用域链的工作原理是从内向外逐层查找变量,直至全局作用域。闭包通过作用域链记住其词法作用域,即使在外部作用域之外执行也能访问内部变量。作用域链有助于变量隔离、模块化和数据隐藏,提高代码的可维护性和可读性。
|
2月前
|
JavaScript 前端开发
JavaScript编程实现tab选项卡切换的效果
JavaScript编程实现tab选项卡切换的效果
|
2月前
|
JavaScript 前端开发
用JavaScript编程控制网页上checkbox选择状态:全选、全部取消、反选
用JavaScript编程控制网页上checkbox选择状态:全选、全部取消、反选
|
2月前
|
JavaScript 前端开发 安全
JavaScript编程实现字符和字符串翻转
JavaScript编程实现字符和字符串翻转