前言
几乎所有编程语言最基本的功能之一,就是能够储存变量当中的值,并且能在之后对这个值进行访问或修改。但是将变量引入程序会引起几个很有意思的问题,也正是我们将要讨论的:这些变量储存在哪里?程序需要时如何找到它们?
这些问题说明需要一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量。这套规则被称为作用域。
那么在哪里而且怎样设置这些作用域的规则呢?
一、作用域是什么
1.1 编译原理
传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为"编译"。
- 分词/词法分析: 将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为 词法单元(token)。
- 解析/语法分析:将第一步完成后得到词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为 抽象语法树(Abstract Syntax Tree,AST)。
- 代码生成:将 AST 转换为可执行代码的过程称被称为代码生成。
实际的编译情况很复杂,这里只是简单的介绍。
1.2 理解作用域
我们可以通过引擎、编译器、作用域对程序的处理过程来进一步理解作用域即它的设置规则。
首先我们需要初步认识引擎、编译器、作用域。
- 引擎:负责整个 JavaScript 程序的编译及执行过程。
- 编译器:负责语法分析及代码生成。
- 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列 查询 ,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
接下来我们来看它们是如何互相合作处理程序 var a = 2;
的。
-
首先编译器会在 当前 作用域中声明一个变量并命名为a(先询问作用域发现 当前 作用域集合中没有a)
-
在运行时引擎会在作用域中 查找 该变量,如果能够找到就会对它赋值。
第二步的查找指的是 LHS查找 ,同理对应的有 RHS查找 。
- LHS查找:为等号后面的变量寻找到存储它的容器;
- RHS查找:查找得到某个变量的值
1.3 作用域嵌套
相信你已经注意到了前面我们一直强调当前作用域,这是因为实际情况中通常需要兼顾几个作用域。
而作用域有以下几种。
- 全局作用域
- 函数作用域
- 块级作用域
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。
当存在作用域嵌套时作用域的 查找规则 是:
- 先在当前作用域中查找,找的到就返回,找不到就去上一级作用域中查找,直到找到全局作用域,还找不到就报错
- 只能从内到外查找,不能从外到内查找
通过上面的介绍我们已经了解到了作用域是什么和作用域的相关规则,接下来我们继续了解作用域的分类,学习这几种作用域是如何定义的,有什么区别。
二、作用域分类
全局作用域
在代码中任何位置都可以访问到的变量即属于全局作用域
那么什么情况会导致声明的变量位于全局作用域呢?
1. 在所有函数和代码块之外(即作用域嵌套的最外层)声明的变量、函数、对象拥有全局作用域
js
// 全局声明的变量
var name='global';
// 全局声明的函数
function foo(){
}
//全局声明的对象
const obj = {
name: "John",
age: 30,
city: "New York",
}
2. 未定义直接赋值的变量自动声明为全局变量,拥有全局作用域
js
function foo(){
// 未定义直接赋值的变量;
a=2
}
foo()
console.log(a)//输出: 2
3. 在浏览器环境中,所有 window 对象的属性拥有全局作用域(node 环境中则换成 global 对象)
js
function foo() {
global.name='global'
}
foo()
console.log(name)//输出:global
函数作用域
在任意代码片段外部添加包装函数,可以将内部的变量和函数定义"隐藏"起来,外部作用域无法访问包装函数内部的任何内容。
js
//全局声明的变量
var a = 2;
function foo() {
//函数内声明的变量
var a = 3;
console.log( a ); // 3
}
foo();//输出:3
console.log( a ); //输出:2
函数嵌套在另一个函数中会发生 作用域的嵌套 。

块级作用域
块作用域指的是变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常指 { .. } 内部)。
需要注意的是只有const和let + { }可以产生块级作用域,var因为存在声明提升不能产生块级作用域。
var的声明提升
js
if(true){
var a = 1;
}
console.log(a);//输出:1
if(true){
let b = 2;
}
console.log(b);//报错:b is not defined
扩展知识
var、let、const的区别:
- let,const 声明不存在声明提升
- let,const 声明的变量不能重复声明(在同一个作用域)
- var声明的变量会被挂载到window上
- const 声明的变量 值不能被修改
看到这里,相信你已经掌握作用域这套管理变量的规则了。