通过一句 var a= 1; 了解JS的执行机制
JavaScript 是一种广泛使用的编程语言,其作用域和变量查找机制是理解代码执行流程的关键。接下来将分享我的一些关于 JavaScript 中的变量声明、赋值、作用域以及变量查找过程的见解,并给出一些相关的例子来说明这些概念,方便大家理解。
1. 变量声明与赋值 ,var a = 1;如何解释?
在 JS 中,变量可以通过 var 关键字声明。例如:
var a = 1;
看似很简单,其实确实不难。这行代码往细了讲,可以分为两个步骤解释:
- 声明:
var a;—— 变量a被声明。 - 赋值:
a = 1;—— 变量a被赋予值 1。
2. 要实现 var a = 1; 哪些东西要为它工作?
在 JS 中,这句 var a = 1; 语句背后涉及多个组件的协同工作,接下来我们将探讨这一过程,了解引擎、编译器和作用域管理如何共同完成这一任务,实现代码的执行的。
1.引擎:CEO
引擎可以类比为一个公司的 CEO,负责整体的工作。它的主要职责包括:代码解析、性能优化以及内存管理。
2. 编译器:CTO
编译器扮演着 CTO 的角色,负责将源代码转换为可执行的机器码。具体来说,编译器的工作可以分为以下几个步骤:
2.1 分词
分词是将源代码分解成一个个有意义的单元(token)。例如,对于 var a = 1; 这行代码,编译器会将其分解为:
var:关键字a:标识符=:赋值操作符1:数字字面量;:语句结束符
2.2 语法分析
在分词之后,编译器会进行语法分析,将 token 序列转换为抽象语法树(AST)。AST 是一种树状结构,表示代码的逻辑结构。
2.3 代码生成
最后,编译器会根据 AST 生成可执行的机器码。这个过程包括优化和翻译,确保生成的代码高效运行。
3. 作用域:COO
作用域扮演着 COO(首席运营官)的角色,它决定了变量在代码中的可见性和生命周期。在 JavaScript 中,变量不会单独存在,而是归属于某个特定的作用域。
3.1 作用域的分类
JavaScript 中主要有两种作用域:
- 全局作用域:在全局范围内声明的变量。
- 局部作用域:在函数或块级范围内声明的变量。
3.2 作用域嵌套与作用域链
作用域嵌套是指在一个作用域内嵌套另一个作用域。作用域链是描述这种嵌套关系的链表结构。例如:
var a = 1;
function foo() {
var b = 2;
function bar() {
var c = 3;
console.log(c); // 输出: 3
console.log(b); // 输出: 2
console.log(a); // 输出: 1
}
bar();
}
foo();
在这个例子中,bar 可以访问 c、b 和 a,因为它们分别位于当前作用域、父级作用域和全局作用域中。
3. 变量查找, LHS 查找和 RHS 查找
变量查找过程可以分为两个阶段:LHS 查找和 RHS 查找。
- LHS 查找(Left-Hand Side) :在赋值操作中查找变量的容器。例如:
a = 1; - RHS 查找(Right-Hand Side) :在取值操作中查找变量的值。例如:
var b = a + 2;
3.1.简单案例 方便理解
对代码中的 LHS 和 RHS 引用进行分析:
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
-
LHS 引用(共 3 处):
- c = ...: 在 var c = foo(2); 这行代码中,c 出现在赋值操作符 = 的左侧,因此它是一个 LHS 引用。 我们想要找到 c 的容器并将其赋值为 foo(2) 的返回值。
- a = 2(隐式变量分配): 当调用 foo(2) 时,会隐式地将参数 2 赋给函数的参数 a。 这个赋值操作也需要进行 LHS 查询来找到 a 的容器。
- b = ...: 在函数 foo(a) 内部,var b = a; 这行代码中,b 出现在赋值操作符 = 的左侧,因此它是一个 LHS 引用。 我们想要找到 b 的容器并将其赋值为 a 的值。
-
RHS 引用(共 4 处):
- foo(2 ...): 在 var c = foo(2); 这行代码中,我们需要获取 foo 的值(也就是函数本身)并执行它。 因此,foo 是一个 RHS 引用。
- = a: 在 var b = a; 这行代码中,我们需要获取 a 的值并将其赋给 b。 因此,a 是一个 RHS 引用。[3]
- a ...: 在 return a + b; 这行代码中,我们需要获取 a 的值并将其与 b 的值相加。 因此,a 是一个 RHS 引用。
- ...b: 在 return a + b; 这行代码中,我们需要获取 b 的值并将其与 a 的值相加。 因此,b 是一个 RHS 引用。
4 引擎和作用域的对话 方便理解的小剧场
function foo(a) {
console.log(a ); //2
}
foo(2);
让我们把上面这段代码的处理过程想象成一段对话,这段对话可能是下面这样的。
5. LHS与RHS运用起来有何不同?
5.1 RHS 查找失败与 ReferenceError
考虑以下代码:
function foo(a) {
console.log(a + b);
b = a;
}
foo(2);
在这段代码中,foo 函数接受一个参数 a,并在控制台输出 a + b 的结果,然后将 a 的值赋给 b。当我们调用 foo(2) 时,会发生什么?
1. 代码结果:
2. 进行变量查找分析:
- RHS 查找:在
console.log(a + b)中,b是 RHS 查找的目标,引擎需要找到b的值。如果b未定义,引擎会抛出ReferenceError: b is not defined。 - LHS 查找:在
b = a中,b是 LHS 查找的目标,引擎需要找到b的容器。即使b未定义,JavaScript 引擎也会在当前作用域中创建一个变量b并赋予值 2。
5.2 LHS 查找与隐式全局变量
考虑以下代码:
function foo() {
b = 2; // LHS 查询,默认声明变量
}
foo();
// LHS b 全局
console.log(b); // 输出: 2
在这段代码中,foo 函数内部有一个赋值操作 b = 2。当我们调用 foo() 时,会发生什么?
1. 代码结果:
2. 进行变量查找分析:
- LHS 查找:在
b = 2中,b是 LHS 查找的目标。即使b在当前作用域中未定义,JavaScript 引擎也会在当前作用域的父级作用域中继续查找。 - 全局作用域:如果在所有作用域中都没有找到变量
b,JavaScript 引擎会在全局作用域中创建一个变量b并赋予值 2。
注意:
"use strict";//严格模式下会抛出ReferenceError错误,在严格模式下,JavaScript 引擎禁止隐式创建全局变量。如果在 LHS 查找过程中没有找到变量b,引擎会立即抛出ReferenceError错误,而不是在全局作用域中创建一个新变量。
6. 变量声明、赋值与调用错误报错的类型?
给出以下代码:
var a; // 声明变量 a,未赋值
a = 2; // LHS 赋值,将 2 赋给变量 a
a(); // 调用 a,引发 TypeError
让我们逐步分析这个过程:
-
var a;: 变量a被声明,但没有赋值,因此它的值为undefined。
-
a = 2;: 变量a被赋予值 2。此时,a的值为2,类型为number。
-
a();: 尝试调用变量a。由于a的值是2,而2不是一个函数,因此会引发TypeError。
7. 结语
在这里,我们探讨了 JavaScript 中的变量声明、赋值、作用域以及变量查找机制的相关概念,理解这些概念对于我们编写高效、无误的 JavaScript 代码至关重要,希望本文能帮助你更好地掌握 JavaScript 的核心机制,理解LHS与RHS的区别,从而更准确的编写和调试代码,希望小伙伴们能够收获到自己想要了解的知识,在前端开发的道路上更进一步,成为一名更牛的前端开发者。