阿瑞斯的BLOG

深入理解JavaScript作用域

关键词
作用域 定义时 变量查找规则 赋值操作基于作用域查找规则 查找目标的值也是基于作用域查找规则

作用域规则

JavaScript作用域是定义时的概念,用于确定在何处以及如何查找变量(标识符),如果是对变量的赋值操作就会进行LHS查询,如果是获取变量的值就会进行RHS查询。

深入理解”定义时”概念

两个函数唯一的区别:在于函数体内部的变量x是否用var重新声明.

当使用默认参数的时候,参数会形成一个单独的作用域,等到初始化结束,这个作用域就会消失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
var x = 1;
function foo(x, y = function() { x = 2}) {
var x = 3;
y();
console.log(x);
}
foo(); // 3
x; // 1
----------------------------------------------
2
var x = 1;
function foo(x, y = function() { x = 2}) {
x = 3;
y();
console.log(x);
}
foo(); // 2
x; // 1

代码的执行过程:参数初始化过程和函数体代码执行过程

对于例1很容易得出错误的结果foo(); // 3 x; // 1

错误的原因就是:对于作用域是定义时概念理解得不深刻。

1
2
3
4
5
6
7
8
9
10
11
12
参数初始化过程:
1.形成一个独立的作用域,当不传递参数y时,使用默认参数值进行初始化
{
let x;
let y = function() {x = 2 };
}
2.代码执行的过程
var x = 3; // 用var重新声明了和参数同名的变量x,则此时参数x被覆盖,变量x和参数x不是同一个x
y(); // 函数调用,对变量x进行赋值操作,这时最容易出错的地方
// 因为y函数内部并没有变量x,则对外层的变量x进行赋值,关键是对哪一个外层的x进行赋值?
// 这里很容易理解成调用时函数y的外部x进行赋值,这个时候就把定义时的概念没有理解清楚。
// 而真实的是此时对参数作用域中的x进行赋值,但因为参数x和变量x不指向同一个x,而函数体内使用的是变量x,所以赋值操作没有生效。

对于例2,因为没有用var重新声明一个同名变量,此时函数体内的x指向参数x,y赋值操作的目标也是参数x,两个x是同一个x,所以y()赋值成功。

关于为什么会错误理解

因为时常弄混淆静态作用域和动态this

动态作用域

JavaScript中的作用域是词法作用域

词法作用域是一套关于引擎寻找变量以及会在何处找到变量的规则。词法作用域最最最最重要的特征是它的定义过程发生在代码的书写阶段(假设你没有使用eval()或with)

动态作用域似乎暗示有很好的理由让作用域作为一个在运行时就被动态确定的形式,而不是在写代码时进行静态确定的形式。

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(a);
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();

词法作用域让foo()中的a通过RHS引用到了全局作用域中的a,因此会输出2.

而动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。动态作用域的查找结果导致a为3.

简单总结区别

词法作用域是在写代码的时候或者说定义时确定的,而动态作用域是在运行时确定的,(this也是!) 词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。