1.JS引擎
我们要想理解JS作用域,必须要从底层执行内核——JS引擎说起。任何一门编程语言都有自己的引擎,在日常学习开发最常用的JS引擎有:Chrome的V8引擎和Node.js引擎,只要内置了JS引擎也就是编译环境,我们就可以执行JavaScript代码了。
2.JS的执行过程
JavaScript的代码执行,从来不是单纯逐行解释运行,而是以作用域为基础框架,配合预解析、执行上下文、变量提升逐级推进。是理解 JS 执行全过程的核心切入点。就以V8引擎为例,来讲解V8引擎是如何执行JS代码的。
- 编译:V8先读取代码,其并不会第一时间执行代码,而是先编译代码。
- 分词:将代码拆分成一个一个词法单元。
- 解析/语法分析:将分好的词法单元组织成一个抽象语法树AST(Abstract Syntax Tree)。
- 执行:根据AST生成代码执行。
话不多说,直接上干货,带你秒懂 JS 引擎咋干活!
var a = 10;
console.log(a);
引擎工作过程:
- 先把整段代码全盘通读一遍;
- 开始拆词分词,拆出:var、a、=、10、console.log(a) 这些最小代码碎片;
- 把碎片按顺序拼装成 AST 语法树,帮自己理好逻辑顺序;
- 按顺序跑代码,打印出 10。
然后再看这段反直觉代码,老 C/Java 选手直接懵圈:
console.log(a);
var a = 10;
放 C、Java 里,这代码早被编译器按在地上摩擦,直接报错 “变量没定义”!但在 JS 里,它不仅不报错,还输出个undefined。这就说明JS引擎在编译阶段优先扫描了var变量,也就是提前访问变量时,引擎就识别到了变量存在,但尚未赋予具体数值,当直接console.log(a)时就默认返回了初始值undefined。
搞懂了 JS 引擎的底层干活逻辑,铺垫已经到位,接下来正式进入本文重头戏 —— 作用域!
3.作用域
3.1全局作用域
- 写在函数、{} 块外面的代码范围,整个代码都能访问。
var globalNum = 10;//全局变量
function fn() {
console.log(globalNum);
}
fn(); // 可以输出 10
console.log(globalNum); // 外面也能用
3.2函数作用域
- 函数作用域:变量只在函数内部有效,外面访问不到,和全局正好相反。
var globalNum = 100;// 全局 global 变量
function foo() {
var loacalNum = 20;// 函数作用域变量,只在函数里能用
console.log(globalNum); // 可以用全局的
console.log(loacalNum); // 可以用自己函数里的
}
foo();
// console.log(localNum); // 报错!外面访问不到函数里的变量
3.3块级作用域
学习块级作用域之前,要先了解什么是let、什么是const
3.3.1 let和const
作为前端开发者,变量声明是每天都会用到的基础操作。ES6 之前,我们只能用 var 声明变量,但它的作用域混乱、变量泄露等问题,曾让无数开发者踩坑。ES6 推出的 let 和 const,彻底解决了 var 的痛点,成为目前前端开发的标准规范。
- let:
- let+{}会创建一个块级作用域,块级作用域中的变量只能在块级作用域中使用。
if (true) {
let a = "let 有块级作用域";
console.log(a); // 内部可访问
}
console.log(a); // 报错:msg is not defined
- let不会带来变量提升。
// var 的情况`
console.log(foo); // 输出undefined`
var foo = 2;`
// let 的情况`
console.log(bar); // 报错ReferenceError`
let bar = 2;`
上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
暂时性死区:ES6 中,
let/const声明的变量,在声明语句执行之前,该变量所处的区域就是暂时性死区,此时访问变量会直接报错,而非像var那样返回undefined。
- let不能像var一样重新声明
//
var a = 10;
var a = 20;
console.log(a); // 输出20
let b = 10;
let b = 20; // 报错,无法重复声明
- const:
- const和let有99%的相似性,不同的是const是常量,不能重新赋值。
const num = 10;
num =20; //此时重新对num赋值就会报错
console.log(num);
- var、let、const 核心区别总结
| 变量 | 作用域 | 暂时性死区 | 重复声明 | 初始赋值 | 重新赋值 |
|---|---|---|---|---|---|
| var | 函数 / 全局 | 无 | 允许 | 可省略 | 可修改 |
| let | 块级作用域 | 有 | 不允许 | 可省略 | 可修改 |
| const | 块级作用域 | 有 | 不允许 | 必须赋值 | 不可修改 |
3.3.2块级作用域详解:
定义:被 { } 大括号包裹的区域,就是块级作用域。if、for、while、单独 {} 都属于块级。
if (true) {
// 块级变量,只在当前 {} 内有效
let a = 10;
const b = 20;
}
// 出了大括号就不能访问
console.log(a); // 报错
console.log(b); // 报错
注意:只有 let / const才有块级作用域,var没有块级作用域,会穿透 {}。
if (true) {
var m = 100; // var 无块级作用域,泄露到外面
let n = 200; // let 有块级作用域,仅限内部
const s = 200; // let 有块级作用域,仅限内部
}
console.log(m); // 100 能访问
console.log(n); // 报错
console.log(s); // 报错
4.总结
- JS 引擎:JS 代码依托 V8、Node.js 等引擎执行,引擎会经过分词→解析生成 AST 抽象语法树→执行代码三个核心步骤。
- JS 执行:特点代码不是逐行直接执行,会先进行预解析编译,存在变量提升机制,这也是var可以先使用后声明、输出undefined的原因。
- 全局作用域:声明在函数、{}代码块外部,整个代码任意位置都可访问。
- 函数作用域:在函数内部用var声明的变量,仅函数内部可访问,外部无法调用。
- 块级作用域:被{}包裹的区域(if/for/while/ 单独大括号),仅 let/const能生成块级作用域,变量只能在当前块内访问;var无块级作用域,会发生变量泄露。
[想深入学习JS,推荐阅读这本经典书籍](《阮一峰 ES6 标准入门教程)