从var a = 1;看LHS与RHS查询:JavaScript变量作用域的奥秘

278 阅读5分钟

前言

几乎所有编程语言最基本的功能之一,就是能够储存变量当中的值,并且能在之后对这个值进行访问或修改。在JavaScript的世界里,每一个看似简单的代码片段背后,都隐藏着复杂的执行机制和精妙的设计。本文将以var a = 1;这一行代码为切入点,带你深入了解JavaScript引擎如何处理变量声明、初始化以及作用域查找,揭开这门语言背后的神秘面纱。

变量声明与初始化

当你在JavaScript中写下var a = 1;时,实际上分为了两个阶段:编译阶段执行阶段

  • 编译阶段var a; 这一部分告诉编译器,我们需要在当前作用域中创建一个名为a的变量。在编译阶段,这个变量会被添加到当前作用域中,但此时它的值为undefined
  • 执行阶段a = 1; 这一部分则是将变量a的值设置为1。在执行阶段,引擎会查找变量a并为其赋值。

作用域:变量的归属地

作用域是指变量或函数的有效范围,即它们可以被访问的区域。JavaScript中的作用域主要有两种:

  • 全局作用域:在整个程序中都可以访问的作用域。
  • 局部作用域(也称为函数作用域):仅在特定函数内部可以访问的作用域。

当一个变量被声明时,它会自动归属于最近的一个作用域。例如,如果在函数内部声明了一个变量,那么这个变量就只能在这个函数内部被访问。请看下面的代码

var a = 1;
var b = 4;
function foo() {
  var a = 2;
  function bar() {
    var a = 3;
    return a + b; 
  }
  console.log(a, b);
}
foo(); // 2 , 4

console.log(a, b);:在 foo 函数内部,打印了局部变量 a 和外部作用域中的变量 b 的值。这里的 a 是 foo 函数内部声明的 a,其值为 2,而 b 是全局作用域中的 b,其值为 4。有小伙伴可能疑问了, return a + b;不是7 吗,有这样的想法是对的,但是这个return的返回值只有在函数bar中结果才会是7

作用域链

在JavaScript中,当尝试访问一个变量时,引擎会从当前作用域开始搜索该变量。如果未能找到,则继续在外层作用域中寻找,直到到达全局作用域。如果仍未找到,则抛出错误。这个查找路径被称为“作用域链”。就像你在初中遇到了自己的白月光小美,而且你还知道她住在哪栋楼,你想找到她就只能从一楼慢慢找上去一样。

8ea37d2a2ca88bde21022a8dde55aa1a.png

LHS与RHS查询

在JavaScript中,对于表达式a = 1;,引擎需要进行两种类型的查询:

  • LHS(Left-hand Side)查询:为了找到一个可以赋值的目标,即等号左侧的部分。例如,在a = 1;中,a需要通过LHS查询来确定其位置以便进行赋值。
  • RHS(Right-hand Side)查询:为了获取一个值,即等号右侧的部分。例如,在a = b + 1;中,b需要通过RHS查询来确定其值。

看完上面这些,可能大多数同学还是感到一阵懵逼,简单的说就是LHS是试图找到变量容器,从而对其赋值,你可以将RHS理解成retrive his source value(取到它的源值)。请看如下的代码:

function foo(a){
  var b = a;
  return a + b;
}
var c = foo(2);

这里一共出现了三次LHS,四次RHS,最明显的两处LHS当然是b= 和c=了,那么还有一处在哪里呢?还有一处隐式调用,foo(2)处其实有a = 2的隐式赋值,这也属于LHS查询。再来分析一下RHS,RHS是取值,=a,a,b,foo(2)四处都是RHS。

LHS和RHS异常

当RHS查询当前作用域时,如果找不到变量,引擎会抛出ReferenceError错误,例如:直接在整个作用域当中打印输出一个没有定义声明的变量或函数就会报错。

console.log(test); // ReferenceError:test is not defined
test(); // ReferenceError:test is not defined

我们在举个栗子:

function foo(num1){
  console.log(num1+num2); // ReferenceError: num2 is not defined
  num2 = num1;
}
foo(10)

以上的代码当中进行了RHS,但仍然无法查找到num2,因为作用域是往上查找到,这里很明显也是找不到的!

但是如果RHS在作用域中查找到变量,但是进行了不规范的操作,比如在末尾加了个(),那么JS引擎会抛出另一种异常TypeError(类型异常)。如下代码:

var test = 100;
test(); // TypeError :test is not a function

当JS引擎执行LHS查询时,在非严格模式下,如果在所有作用域中找不到目标变量,就会在全局作用域中创建一个与该变量名相同的全局变量,并将其返回给JS引擎。但如果是严格模式下则会出现ReferenceError 错误

"use strict";
// 严格模式时, LHS 查询 失败的时候,会爆出 ReferenceError 错误,报错
function foo() {
  b = 2; // ReferenceError: b is not defined
}
foo();

JavaScript引擎的角色

在JavaScript中,整个程序的执行是由一个名为V8的引擎(以Chrome浏览器为例)控制的。你可以想象V8引擎作为CEO,负责协调各个组件的工作,编译器作为CTO,负责将源代码转换成机器可执行的代码,同时处理变量的声明和初始化,作用域作为COO,负责管理变量的生命周期,确保变量能够在正确的作用域内被访问,看下图:

4849da892991eb582e227cc0d70642cf.png

小结

通过本文的探讨,我们可以看到,即使是像var a = 1;这样简单的一行代码,背后也涉及到了JavaScript引擎、编译器以及作用域等多个层面的协作。理解这些底层机制,不仅能够帮助开发者更高效地编写和调试代码,还能避免常见的作用域相关错误。希望本文能为你打开JavaScript世界的大门,让你在编程的道路上更加得心应手。

th.jpg

点个赞吧