网站

JavaScript 作用域 - W3School

作用域 - JavaScript | MDN

引入

写代码时,变量不是在哪里都能用的。

有些地方能访问到,有些地方就找不到这个变量了。

作用域就是用来规定变量在哪些地方可以被访问的。

正文

定义

定义

作用域是指变量和函数可以被访问的代码区域。JS 中有三种作用域:全局作用域、函数作用域和块级作用域。

全局作用域

在所有函数和 {} 外面声明的变量,属于全局作用域。

全局变量在任何地方都能被访问。

let name = "Tom";
 
function sayHello() {
  console.log(name);
}
 
sayHello();
console.log(name);

name 在全局作用域中声明,函数里面和外面都可以访问到它。

全局变量虽然方便,但是用多了容易出问题。

任何地方都能改它,出了 bug 也不好找是谁改的。

函数作用域

在函数内部声明的变量,只能在函数内部访问。

function test() {
  let message = "你好";
  console.log(message);
}
 
test();
console.log(message);

最后一行会报错,因为 message 只在 test() 函数内部存在。

函数执行结束后,函数内部的变量就不能再访问了。

var 声明的变量也是函数作用域。

function test() {
  var age = 18;
  console.log(age);
}
 
test();
console.log(age);

同样报错,var 的变量也只在函数内部有效。

块级作用域

{} 包起来的代码块就是一个块级作用域。

letconst 声明的变量有块级作用域。

{
  let count = 10;
  console.log(count);
}
 
console.log(count);

最后一行报错,count 只在 {} 里面存在。

常见的块级作用域出现在 ifforwhile 等语句中。

if (true) {
  let secret = "密码";
  console.log(secret);
}
 
console.log(secret);

secret 只在 if 的大括号里有效,外面访问不到。

var 没有块级作用域

varletconst 最大的区别就在这里。

var 不受 {} 的限制,只受函数的限制。

{
  var color = "red";
}
 
console.log(color);

color 可以正常输出 "red"

因为 var 不认 {},它只认函数。

再看一个对比:

function test() {
  if (true) {
    var a = 1;
    let b = 2;
  }
 
  console.log(a);
  console.log(b);
}
 
test();

a 可以输出 1,因为 var 的作用域是整个 test() 函数。

b 会报错,因为 let 的作用域只在 if{} 里。

三种作用域对比

作用域类型声明方式有效范围示例
全局作用域函数和 {} 外部所有地方最外层声明的变量
函数作用域varletconst函数内部函数里声明的变量
块级作用域letconst{} 内部iffor 里声明的变量

嵌套作用域

作用域可以一层套一层。

let global = "全局";
 
function outer() {
  let outerVar = "外层";
 
  function inner() {
    let innerVar = "内层";
    console.log(global);
    console.log(outerVar);
    console.log(innerVar);
  }
 
  inner();
}
 
outer();

inner() 可以同时访问外层函数和全局的变量。

但是外层函数访问不了 innerVar,全局也访问不了 outerVar

内层可以往外找,外层不能往内找。

for 循环中的作用域

这个例子很能说明 varlet 的区别。

for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}

输出:

3
3
3

var 没有块级作用域,循环结束后 i 变成了 3,三个回调共用同一个 i

换成 let

for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}

输出:

0
1
2

let 每次循环都会创建一个新的块级作用域,每个回调记住的是各自那一轮的 i

特点

  • 全局作用域的变量哪里都能访问
  • 函数作用域的变量只在函数内部有效
  • letconst 有块级作用域,var 没有
  • 内层作用域可以访问外层作用域的变量
  • 外层作用域不能访问内层作用域的变量
  • 尽量少用全局变量,避免意外修改

理解

可以把作用域理解成一栋楼里的房间

全局作用域是大厅,所有人都能进去。

函数作用域是一个个独立的房间,只有进了那个房间才能看到里面的东西。

块级作用域是房间里的小隔间,letconst 的东西放在隔间里,出了隔间就找不到了。

var 的东西放在房间里,不管隔间,整个房间都能看到。

你在小隔间里可以看到房间和大厅的东西,但在大厅里看不到房间里的东西。

引出

理解了作用域之后,可以继续学习 JS 作用域链,知道当变量在当前作用域找不到时,JS 是怎么一层一层往外找的。

也可以学习 JS 闭包,理解函数怎么记住它创建时所在作用域的变量。