04提升与严格模式

「还没声明就能调用?」——这是变量提升(Hoisting)。 「写错名字居然不报错?」——这是宽松模式的锅。本课讲清提升规则、暂时性死区 TDZ、 严格模式到底改了什么,帮你避开一批「诡异但常见」的 bug。

约 12 分钟 3 个动手实验 避坑必读

一、什么是变量提升

JS 引擎在执行代码前会先「扫一遍」作用域,把声明(不是赋值)提到当前作用域顶部。 函数声明整体提升,变量只提升声明、不提升赋值。

二、var / let / const / function 提升对照

声明方式是否提升提升后初始值声明前访问
var ✓ 声明提升 undefined 得到 undefined(不报错)
let / const ✓ 提升但进 TDZ 无(死区) ReferenceError
function 声明 ✓ 整体提升 完整函数 可以直接调用
const fn = () => … 同 let TDZ ReferenceError
import ✓ 模块提升 绑定已就绪 可直接用(模块顶层)
四种声明的提升差异:点按钮看声明前访问各自的返回

三、暂时性死区(TDZ)

let / const 从作用域开始到声明语句之间有一段 「不能访问」的区域,叫暂时性死区(Temporal Dead Zone)。 这是故意设计的——让「先用后声明」尽早报错,而不是悄悄变成 undefined。

看到 ReferenceError: Cannot access 'x' before initialization 就是踩了 TDZ。解法:把声明挪到使用之前,别用 var「绕过」。

四、函数提升的优先顺序

同一作用域里同时出现 function 声明和 var 同名变量时:

  1. 先处理 function 声明(整体提升)
  2. 再处理 var 声明(若同名则跳过,不覆盖已提升的函数)
  3. 最后按代码顺序执行赋值
避免同名混用。项目里要么纯 function 声明工具函数, 要么纯 const fn = () =>,别混。

五、严格模式:更干净的 JS

文件或函数顶部写 'use strict' 开启严格模式。 ES Module 默认严格,现代项目基本都在严格模式下运行。

宽松模式会静默犯错

给未声明变量赋值 → 创建全局变量;重复参数名不报错;八进制字面量 010 可用;with 可用;this 默认 window。

严格模式直接报错

未声明赋值 → ReferenceError;重复参数 → SyntaxError;八进制要写 0o10;禁 with;独立调用 this = undefined。

严格模式主要改动速查
行为宽松严格
未声明变量赋值偷偷变全局ReferenceError
只读属性写入静默失败TypeError
删除不可删属性静默失败TypeError
重复参数名允许SyntaxError
八进制字面量010 = 8语法错误,用 0o10
with允许语法错误
独立函数的 thiswindowundefined
新增全局eval 污染外层eval 有独立作用域
宽松 vs 严格:同一个「给未声明变量赋值」的动作,结果完全不同

六、怎么开启 / 何时自动开启

  • 整个文件:顶部写 'use strict';
  • 单个函数:函数体第一行写
  • ES Module(<script type="module"> / import/export)默认严格
  • class 体内部默认严格
  • 老式全局脚本默认宽松——迁移老代码时留意
新项目直接用 Module,根本不用手写 'use strict'。 维护老代码时,可以在整个文件加一行,把「静默 bug」变成「显式报错」。

七、和前面知识的连接

  • 作用域:TDZ 只存在于 let/const 的块级/函数作用域;var 没有 TDZ。
  • this:严格模式独立调用 this = undefined,影响默认绑定规则。
  • 模块:Module = 严格模式 + 独立作用域,两边一起记。
  • 调试:看到 TDZ / 严格模式报错,先查声明位置,再查是否「未声明赋值」。

八、本章小结

  • 提升:声明(不是赋值)被提前。function 整体提升,var 只提声明得 undefined。
  • let/const 有 TDZ,声明前访问 → ReferenceError(故意设计)。
  • 同名 function 声明会赢过 var 的声明提升,但后续赋值会覆盖。
  • 严格模式禁止未声明赋值、重复参数、with;this 独立调用为 undefined。
  • ES Module / class 默认严格,新项目自然干净。
  • 调试口诀:undefined 想到 var 提升;before initialization 想到 TDZ; is not defined 想到未声明。
动手练习: ① 在控制台分别用 var / let 做「声明前访问」,对照报错信息; ② 写一个严格模式函数,故意给未声明变量赋值,看抛出什么; ③ 解释为什么 function foo(a = b, b = 1) 会报错,而调换参数顺序就不会。