JS 变量与作用域知识点大全详解
一、变量声明的三种方式:var /let/const
1. var(ES5 旧声明方式)
特性
-
变量提升 :声明会提升到当前作用域顶部,初始值
undefinedconsole.log(a); // undefined
var a = 10; -
函数作用域:仅区分函数,无块级作用域(if/for 块内声明全局可见)
if(true){
var num = 100;
}
console.log(num); // 100 块外可访问 -
允许重复声明:同一作用域重复声明不报错,会覆盖值
var x = 1;
var x = 2;
console.log(x); // 2 -
挂载到全局 window:全局
var变量会成为 window 属性var test = 99;
console.log(window.test); // 99
2. let(ES6 块级变量)
-
块级作用域 :
{}、if、for、while 内部声明,块外无法访问 -
存在变量提升,但暂时性死区 TDZ:声明前访问直接报错,不能提前使用
console.log(b); // Uncaught ReferenceError
let b = 20; -
禁止重复声明:同一作用域重复声明直接报错
-
全局 let 不会挂载到 window 对象
3. const(ES6 常量)
-
拥有 let 全部特性:块级作用域、暂时性死区、不可重复声明
-
声明时必须赋值,不赋值直接报错
const c; // 语法错误
-
不能直接重新赋值 :基础类型值无法修改;引用类型(对象 / 数组)地址不变即可修改内部属性
// 基础类型 不可修改
const age = 18;
age = 20; // 报错// 引用类型 内部可修改
const user = { name:"小明" };
user.name = "小红"; // 合法,只是修改属性,地址没变
user = {}; // 报错,重新赋值改变地址
var /let/const 对比速查表
表格
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数级 | 块级 | 块级 |
| 变量提升 | ✅ 提升,值 undefined | ✅ 提升,TDZ 死区 | ✅ 提升,TDZ 死区 |
| 重复声明 | 允许 | 禁止 | 禁止 |
| 全局挂载 window | ✅ | ❌ | ❌ |
| 重新赋值 | ✅ 任意修改 | ✅ 任意修改 | ❌ 不可重赋值 |
| 初始赋值 | 可选 | 可选 | 必须赋值 |
二、变量提升(Hoisting)
1. 概念
JS 代码执行前会进行预编译,扫描当前作用域所有变量、函数声明,提前存入内存,这就是变量提升。 赋值操作不会提升,只有声明提升。
2. 提升优先级
函数声明 > var 变量声明
console.log(fn); // 完整函数
var fn = 10;
function fn(){}
3. 暂时性死区 TDZ(let/const 独有)
从作用域顶部到 let/const 声明语句之间的区域,称为暂时性死区,该区域内禁止访问变量,触发引用错误。 目的:杜绝变量提前使用,规范代码书写顺序。
三、作用域
1. 作用域定义
变量、函数可被访问的有效代码区域,用来隔离变量,不同作用域同名变量互不干扰。
2. 四大作用域分类
(1)全局作用域
- 页面打开自动生成,整个 JS 文件都能访问;
- 直接在 script 顶层声明的变量 / 函数属于全局;
- 生命周期:页面关闭才销毁;
- 弊端:全局变量过多容易出现命名冲突、污染全局。
(2)函数作用域(ES5)
- 每个函数调用时生成独立作用域;
var变量仅在当前函数内可用,函数外部无法访问;- 函数执行完毕,内部变量自动销毁,节省内存。
(3)块级作用域(ES6 新增,let/const)
{} 包裹的代码块(if、for、while、单独大括号)形成独立作用域,仅 let/const 生效。
{
let msg = "块内";
}
console.log(msg); // 报错,块外不可访问
(4)模块作用域(ES6 Module)
每个 .js 文件导入导出后自成模块,内部变量默认隔离,需 export 导出才能外部访问。
3. 作用域链
形成规则
函数嵌套时,内层函数作用域保存外层函数作用域引用,一层一层向上查找,串联形成作用域链。
变量查找规则(就近原则)
- 优先在当前作用域查找变量,找到直接使用;
- 当前没有,沿着作用域链向上一层外层作用域查找;
- 一直找到全局作用域,仍未找到则报错
ReferenceError。
示例:
let a = 10; // 全局
function outer(){
let a = 20; // 外层函数
function inner(){
console.log(a); // 20,优先取就近外层,不会找到全局
}
inner();
}
outer();
四、执行上下文(作用域运行载体)
1. 概念
代码执行时创建的环境载体,作用域是静态代码区域,执行上下文是代码运行时动态环境。 分两类:
- 全局执行上下文:页面加载自动创建,唯一;
- 函数执行上下文:每次调用函数都会新建,调用结束销毁。
2. 执行上下文三部分
- 变量对象 VO:存放当前作用域变量、函数、形参;
- 作用域链:自身 VO + 所有外层作用域 VO;
- this 指向:全局 window、函数调用者、构造函数实例。
3. 调用栈(执行栈)
先进后出,存放所有正在运行的执行上下文:
- 全局上下文最先入栈,永远在栈底;
- 调用函数,函数上下文压入栈顶;
- 函数执行完毕,上下文弹出销毁;
- 页面关闭,全局上下文出栈。
五、自由变量 & 闭包(作用域延伸重点)
1. 自由变量
一个变量在当前作用域没有定义,去外层作用域取值,该变量就是自由变量。 自由变量取值定义时的作用域,和调用位置无关。
2. 闭包
定义
内层函数能够访问外层函数作用域变量,且内层函数在外层函数执行完毕后依然被外部引用,形成闭包。 形成条件:函数嵌套 + 内层函数引用外层变量 + 内层函数外部执行。
闭包作用
- 保存私有变量,实现数据私有化;
- 延长变量生命周期,外层函数销毁,变量依然存在;
- 模块化、防抖节流、封装工具函数。
闭包弊端
变量长期驻留内存,不及时释放易造成内存泄漏,不用时手动置空引用销毁。
示例:
function count(){
let num = 0; // 私有变量
return function(){
num++;
console.log(num);
}
}
const add = count();
add(); //1
add(); //2
六、变量生命周期
- 全局变量:页面初始化创建,页面关闭销毁;长期占用内存,少用。
- 函数局部变量(var/let/const):函数调用时创建,函数执行结束自动销毁。
- 闭包内变量:函数执行完不销毁,直到闭包引用断开才释放。
- 块级变量:代码块执行结束立刻销毁。
七、污染全局作用域的场景与解决方案
污染场景
-
直接使用
var声明全局变量; -
函数内不声明直接赋值(隐式全局变量)
function fn(){
test = 100; // 未声明,自动挂载window,全局污染
} -
无模块化时多个 JS 文件同名变量覆盖。
解决方案
- 全部使用
let / const替代 var; - 严格模式
use strict,禁止隐式全局变量(未声明直接赋值直接报错); - 使用 IIFE 立即执行函数隔离作用域;
- ES6 Module / CommonJS 模块化开发;
- 统一命名空间封装变量。
八、严格模式 use strict 对变量的限制
- 禁止不声明直接赋值创建隐式全局变量;
- 禁止删除变量
delete x; - 函数参数不能重名;
- 静态绑定作用域,不允许 with 篡改作用域链。
九、高频面试核心总结
- 声明优先级:const > let 优先使用,减少 var;常量用 const,可变变量用 let。
- 作用域查找遵循就近原则,沿着作用域链向上查找。
- 变量提升仅提升声明,赋值不动;let/const 存在暂时性死区,不能提前访问。
- 作用域是静态代码划分,作用域链在函数定义时生成,不是调用时。
- 闭包本质是作用域链的保留,可私有化数据,但需注意内存泄漏。
- var 无块级作用域,容易全局污染,ES6 开发完全弃用 var。