JavaScript作用域

作用域是什么?

作用域是当前的执行上下文,值和表达式在其中可见可被访问。如果一个变量或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。

作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少命名冲突


JS作用域分类

JavaScript 的作用域分为四种:全局作用域、函数作用域、块级作用域、模块作用域。

除了全局作用域以外的作用域,从意义上都可以叫局部作用域。在全局定义的变量叫全局变量,在局部作用域定义的变量可以叫局部变量

全局作用域

简单来说,直接编写在 script 标签之中和单独 JS 文件中的代码,都是全局作用域。
全局作用域在页面打开时创建,页面关闭时销毁

1
2
3
4
5
6
7
var me = "今晚不熬夜!";
function fun(){
var a = "我是局部变量!";
console.log(me);
}
fun(); // 今晚不熬夜!
console.log(a); // 报错

在全局作用域下声明的变量叫做全局变量,全局变量在全局(代码的任何位置)下都可以使用,但是全局作用域中无法访问到局部作用域中的变量。

函数作用域

在函数内部就是函数作用域。调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁,调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的。

1
2
3
4
5
6
7
function fun(){
a = "我是全局变量了!";
var b = "我是局部变量!";
console.log(a,b);
}
fun(); // 我是全局变量了! 我是局部变量!
console.log(a); // 我是全局变量了!

(注意)如果在函数内部,没有使用var、let、const关键字声明直接赋值的变量也属于全局变量。(不建议使用)

块级作用域

用一对花括号(一个代码块)创建出来的作用域,注意只有用 letconst 声明的变量才属于块级作用域。

1
2
3
4
5
6
7
8
9
10
11
12
{
var a = "我是var";
let b = "我是let";
const c = "我是const";
function fun(){
console.log("哈哈哈");
}
}
fun(); // 哈哈哈
console.log(a); // 我是var
console.log(b); // 报错
console.log(c); // 报错

在块级作用域中,没有使用let、const定义的变量、函数都将成为全局变量

模块作用域

模块模式中运行代码的作用域。在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,外部文件是访问不到的,也无法访问外部。这种模块级别的访问限制,叫做模块作用域

m.js

1
2
3
4
5
var a = "我在模块里";
console.log(a); // 我在模块里
export default function(){
console.log("我是模块里的函数");
}

app.js

1
2
3
4
import fun from "./m"

fun(); // 我是模块里的函数
console.log(a); // 报错

上面有一个m.js模块,里面定义了一个变量a和暴露了一个函数,然后app.js导入该模块,由于在模块作用域中m.js模块只暴露了函数,所以a变量在app.js中无法访问。如果看不明白,那你应该看看JS模块化


作用域链

作用域链,简单来说就是,当局部作用域访问变量时,首先看当前作用域下又没有这个变量,如果有就用该变量,没有就访问外层作用域,直到全局作用域为止。(就近原则

1
2
3
4
5
6
7
8
9
let b = "我是全局变量";
{
let a = "我是外层";
{
let a = "我是内层";
console.log(b); // 我是全局变量
console.log(a); // 我是内层
}
}

静态作用域

静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域。(JavaScript就是静态作用域,也称词法作用域

1
2
3
4
5
6
7
8
9
10
11
12
let a = "我是全局变量!";

function fun(){
console.log(a);
}

function test(){
var a = "我是局部变量!";
fun();
}

test(); // 我是全局变量!

大多数现在程序设计语言都是采用静态作用域规则,比如:C、C++、Python、Java、JavaScript、Lua

动态作用域

虽然JavaScript用的是静态作用域,但我觉得还是有必要讲讲什么是动态作用域
动态作用域下函数的作用域在函数调用的时候才决定的。看看下面bash代码,注意是bash bash

1
2
3
4
5
6
7
8
9
10
11
# scope.bash
value=1
function foo () {
echo $value;
}
function bar () {
local value=2;
foo;
}
bar
# bash scope.bash 结果为2