深入理解JavaScript作用域:词法作用域与动态作用域详解

作者:青柠时间:2026-10-31 01:57:00浏览:524

本文深入解析JavaScript中的两种核心作用域模型:词法作用域与动态作用域。详细阐述词法作用域由代码书写位置决定,通过嵌套气泡结构进行标识符查找;对比动态作用域基于调用栈的运行时行为。通过具体代码示例说明变量遮蔽、全局变量访问及this机制混淆等问题,帮助开发者清晰理解变量查找过程。

前面的话

词法作用域与动态作用域概述

在JavaScript开发中,作用域的混乱往往源于对变量查找规则的误解:开发者常混淆依据函数嵌套位置还是调用顺序来查找变量。加之this机制的干扰,使得变量引用极易出错。这本质上是由两种不同的作用域工作模型引起的:词法作用域和动态作用域。明确区分这两种模型,是掌握变量查找机制的关键。本文作为“深入理解JavaScript作用域”系列的第二篇,将重点探讨词法作用域与动态作用域。

词法作用域详解

词法作用域

在编译器工作的第一阶段,即分词(或词法分析)阶段,由字符组成的字符串会被分解为独立的词法单元。这一概念是理解词法作用域的基础。

简而言之,词法作用域是指在代码编写阶段(词法分析阶段)就已经确定的作用域。它由变量和块作用域在代码中的书写位置决定,因此当词法分析器处理代码时,作用域结构保持不变。

关系

无论函数在何处被调用,也无论其调用方式如何,其词法作用域仅由函数声明时所处的位置决定。


function foo(a) {
var b = a * 2;
function bar(c) {
console.log( a, b, c );
}
bar(b * 3);
}
foo( 2 ); // 2 4 12

在此示例中,存在三个逐级嵌套的作用域。为了便于理解,可以将它们想象为一系列逐级包含的气泡。

每个作用域气泡由其对应作用域块代码的书写位置决定,它们呈现逐级包含的关系。

气泡1包含整个全局作用域,其中仅有一个标识符:foo。

气泡2包含由foo创建的作用域,其中包含三个标识符:a、bar和b。

气泡3包含由bar创建的作用域,其中仅有一个标识符:c。

查找

作用域气泡的结构及其相互位置关系为引擎提供了足够的位置信息,引擎利用这些信息来定位标识符。

在执行代码片段中的console.log(...)声明时,引擎需要查找a、b和c三个变量的引用。引擎首先从最内部的作用域,即bar()函数的作用域开始查找。由于在此处未找到a,引擎会沿作用域链向上移动到其嵌套的foo()作用域中继续查找,并在此处找到了a,从而使用该引用。对b而言,情况相同。而对于c,引擎在bar()函数中直接找到了它。

[注意]词法作用域的查找仅针对一级标识符。如果代码引用了foo.bar.baz,词法作用域查找仅试图查找foo标识符;找到该变量后,对象属性访问规则将分别接管对bar和baz属性的访问。


foo = {
bar:{
baz: 1
}
};
console.log(foo.bar.baz);//1

遮蔽

作用域查找从运行时所处的最内部作用域开始,逐级向外(或向上)进行,直到遇见第一个匹配的标识符为止。

在多层嵌套作用域中可以定义同名的标识符,这被称为“遮蔽效应”,即内部标识符会“遮蔽”外部标识符。


var a = 0;
function test(){
var a = 1;
console.log(a);//1
}
test();

全局变量会自动成为全局对象的属性,因此可以不直接通过全局对象的词法名称,而是间接通过对全局对象属性的引用来访问它。


var a = 0;
function test(){
var a = 1;
console.log(window.a);//0
}
test();

利用这种技术,可以访问那些被同名变量遮蔽的全局变量。然而,如果非全局变量被遮蔽,则无论如何都无法被访问到。

动态作用域与JavaScript的this机制

动态作用域

JavaScript使用的是词法作用域,其最重要的特征是定义过程发生在代码书写阶段。

那么,为何要介绍动态作用域?实际上,动态作用域是JavaScript另一个重要机制this的“表亲”。许多作用域的混乱源于将词法作用域与this机制混淆,导致难以区分。

动态作用域并不关心函数和作用域是如何声明的,也不关心它们在何处声明,只关心它们从何处被调用。换句话说,作用域链是基于调用栈的,而不是基于代码中的词法嵌套结构。


var a = 2;
function foo() {
console.log( a );
}
function bar() {
var a = 3;
foo();
}
bar();

【1】如果处于词法作用域(即当前的JavaScript环境),变量a首先在foo()函数中查找,未找到。于是顺着作用域链到全局作用域中查找,找到并赋值为2。因此控制台输出2。

【2】如果处于动态作用域,同样地,变量a首先在foo()中查找,未找到。此时会顺着调用栈,在调用foo()函数的地方,即bar()函数中查找,找到并赋值为3。因此控制台输出3。

小结:两种作用域的区别,简而言之,词法作用域是在定义时确定的,而动态作用域是在运行时确定的。

以上所述是小编给大家介绍的深入理解javascript作用域第二篇之词法作用域和动态作用域,希望对大家有所帮助,如果大家想了解更多内容敬请关注本站!

作者最新文章