Skip to main content

06 作用域链:V8实现查找变量

作用域链就是将一个个作用域串起来,实现变量查找的路径。

作用域就是存放变量和函数的地方,全局环境有全局作用域,全局作用域中存放了全局变量和全局函数。每个函数也有自己的作用域,函数作用域中存放了函数中定义的变量。

var name = "global name";
var type = "global";

function foo() {
var name = "foo";
console.log(name);
console.log(type);
}

function bar() {
var name = "bar";
var type = "function";
foo();
}
bar();

在 foo 函数中使用了变量 name,V8 先使用 foo 函数内部定义的变量 name,那么 type 呢?

函数作用域和全局作用域

每个函数在执行时都需要查找自己的作用域,称为函数作用域,在执行阶段,执行一个函数时,当该函数需要使用某个变量或者调用了某个函数时,便会优先在该函数作用域中查找相关内容。

var x = 4;
var test;
function test_scope() {
var name = "foo";
console.log(name);
console.log(type);
console.log(test);
var type = "function";
test = 1;
console.log(x);
}
test_scope();

V8 执行 test_scope 函数时,在编译阶段会为 test_scope 函数创建一个作用域,在 test_scope 函数中定义的变量和声明的函数都会放到该作用域中。

如果在当前函数作用域中没有查找到变量,V8 会去全局作用域中去查找,这个查找的线路称为作用域链。

全局作用域是在 V8 启动过程中就创建了,且一直保存在内存中不会被销毁的,直至 V8 退出。 而函数作用域是在执行该函数时创建的,当函数执行结束之后,函数作用域就随之被销毁掉了。


V8 启动之后就进入正常的消息循环状态,这时候就可以执行代码了,V8 会先解析顶层 (Top Level) 代码,在顶层代码中定义了变量 x,V8 将变量 x 添加到全局作用域中。

作用域链是怎么工作的

V8 启动,创建全局作用域:

消息循环系统开始工作,执行代码。V8 先编译顶层代码,在编译过程中会将顶层定义的变量和声明的函数都添加到全局作用域中:

V8 进入执行状态:

// 解析阶段,实现变量提升
var name = undefined;
var type = undefined;
function foo() {
var name = "foo";
console.log(name);
console.log(type);
}
function bar() {
var name = "bar";
var type = "function";
foo();
}

// 执行阶段
name = "极客时间";
type = "global";
bar();

当 V8 执行 bar 函数时,也需要经历两个阶段:编译和执行。在编译阶段,V8 会为 bar 函数创建函数作用域:

进入 bar 函数的执行阶段,在 bar 函数中,只是简单地调用 foo 函数,因此 V8 又开始执行 foo 函数。同样在编译 foo 函数的过程中,会创建 foo 函数的作用域:

JavaScript 是基于词法作用域的,词法作用域就是指,查找作用域的顺序是按照函数定义时的位置来决定的。bar 和 foo 函数的外部代码都是全局代码,所以无论是在 bar 函数中查找变量,还是在 foo 函数中查找变量,其查找顺序都是按照当前函数作用域–> 全局作用域这个路径来的。

词法作用域是根据函数在代码中的位置来确定的,作用域是在声明函数时就确定好的了,所以也将词法作用域称为静态作用域。