作用域
作用域指的是程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。
在 JavaScript 中,作用域分为三种:
- 函数作用域
- 全局作用域
- 块级作用域
函数作用域
每个函数都会创建自己的作用域,在函数内部声明的变量,只能在函数内部访问,函数外部无法访问。
// 此处无法使用bar变量
function foo () {
var bar = 'bar';
// 此处可以使用bar变量
}由于只能在函数内部识别局部变量,因此能够在不同函数中使用同名变量。
在函数开始时会创建局部变量,在函数完成时会删除它们。
全局作用域
函数之外声明的变量,会成为全局变量,即网页的所有脚本和函数都能够访问它。
var bar = 'bar';
// 此处可以使用bar变量
function foo ()
// 此处也可以使用bar变量
}块级作用域
通过关键字let、const即可创建块级作用域,所声明的变量在指定块的作用域外无法被访问。
块级作用域在如下情况被创建:
在一个函数内部
在一个代码块内部,即
{}
if (true) {
let bar = 'bar';
// 此处可以使用bar变量
} else {
// 此处无法使用bar变量
}
// 此处无法使用bar变量块级作用域有以下几种特点:
- 不会变量提升
- 不能重复声明
作用域链
当我们在函数中访问一个变量时,首先会在当前的函数作用域中查找,如果没找到,则会到父级作用域中查找,以此类推向上查找形成的链条就是作用域链。
function foo (a) {
var b = a * 2;
function bar (c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); // 2 4 12上述代码中,我们可以看到在 bar 函数的函数作用域中只有形参 c,a 和 b 变量都是从外部函数 foo 的函数作用域中获取的。
因此这段代码形成的作用域链如下:
- 全局作用域
- foo 函数作用域
- bar 函数作用域
词法作用域
JavaScript 的作用域就是词法作用域。
词法作用域指的是,定义表达式并能被访问的区间。简单来说,一个声明(变量、函数)的词法作用域就是它被声明时所在的作用域。
词法作用域又叫静态作用域,与之对应的就是动态作用域,那么它们的区别是什么呢?
这里通过一个代码示例来说明:
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
// 代码运行结果是?上述代码中,一共有三个作用域:
- 全局作用域
- foo 函数作用域
- bar 函数作用域
其中 foo 函数中访问了本地作用域中不存在的变量 value,那么根据作用域链,应该要去 foo 函数的父级作用域中查找。
问题来了,foo 函数的父级作用域是什么呢?是它调用时所在的 bar 函数作用域?还是它定义时所在的全局作用域?
问题的关键就在于词法作用域,在我们介绍词法作用域的时候已经说过,一个声明(变量、函数)的词法作用域就是它被声明时所在的作用域。
因此,foo 函数的词法作用域就是在声明 foo 函数所在的位置,即全局作用域。那么 foo 函数的父级作用域就是全局作用域。代码的运行结果是1。
如果是动态作用域,上面的代码运行结果应该是2。但是动态作用域只有少数的语言(如 bash 脚本)使用。
作用域和执行上下文的区别
作用域是我们能够访问的变量对象,执行上下文是执行环境。
作用域是在定义时确定的,执行上下文是在调用时确定的。
总结
作用域指的是我们有权访问的变量对象(Variable Object)。
JavaScript 中,作用域分为三种:
- 函数作用域
- 全局作用域
- 块级作用域
当我们在函数中访问一个变量时,首先会在当前的函数作用域中查找,如果没找到,则会到声明该函数的作用域中查找,以此类推向上查找形成的链条就是作用域链。