一、什么是作用域

所谓的作用域,实际上就是指程序代码中定义变量所在的区域。
作用域又决定了如何来查找变量,也就是说作用域决定了当前执行代码对变量的访问权限。

二、作用域的模式

作用域模式可分为两种,分别是词法/静态作用域(lexical scoping);与之对应的则是动态作用域
其中,词法/静态作用域包含函数作用域、以及块作用域。

词法作用域,指的是变量作用域的定义由代码书写阶段决定。

动态作用域,指的是变量作用域的定义由代码执行阶段决定。

如图所示:

我们来看如下代码来对两种作用域模式进行分析:

var num = 100;

function test(){
  var num = 1;
  print();
}

function print() {
  console.log(num);
}

test()
// 结果如何呢???

我们来分析一下

假设JavaScript采用的是词法/静态作用域,实际的执行过程应该如下:

首先执行 test 函数,再执行 print 函数,从 print 函数内部查找是否有局部变量  num,如果没有,就根据代码书写的位置,查找上层代码,也就是 num 等于 100,所以结果会打印 100。

假设JavaScript采用的是动态作用域,实际的执行过程应该如下:

首先执行 test 函数,再执行 print 函数,从 print 函数内部查找是否有局部变量 num,如果没有,就从调用 print 函数的 test 函数内部查找,也就是 num 等于 1,所有打印结果是 1。

通过编译执行以上代码,我们发现最终的执行打印的结果为 100,也就说明 num 作用域的定义发生在代码书写阶段。

虽然说有两种作用域模式,实际上在JavaScript中,并不存在动态作用域,它只有词法作用域,但是this的这种机制,在某种程度上和动态作用域有相似之处。

三、块作用域

ES6 引入了 let 和 const 关键字后,JavaScript 也就有了块级作用域。简单的来说,除函数以外,任何由 {} 所定义的代码块,都具有块作用域的特性。

1、普通代码块

{
  let i = 10;
}
console.log(i); // 报错:i is not defined 无法输出打印

2、逻辑代码块

if (true) {
  let a = 10;
}
console.log(a);  // 报错:a is not defined   无法输出打印

for (var i = 0; i < 3; i++) {
  let k = 10;
}
console.log(k);  //  报错:k is not defined 无法输出打印

除以上两种情况以外,还包含,switch、while、do while、try catch 等

四、函数作用域

JavaScript中,在函数内部声明的变量只能影响到变量所在函数体本身,无法从外部对函数内部的变量进行调用,被称为函数作用域;

简单的说,函数作用域就是由函数代码块产生的

function test () {
  let num = 10;
}
test()
console.log(num);  //  报错:num is not defined 无法输出打印

五、总结

1、作用域工作模式:词法/静态作用域、动态作用域
2、词法/静态作用域:函数作用域、块作用域
3、JavaScript 没有动态作用域
4、词法/静态作用域的本质在于代码块在何处声明
5、动态作用域的本质在于代码块在何处调用
6、词法/静态作用域链基于作用域嵌套
7、动态作用域链基于调用栈