接续之前一篇 <JavaScript 内存优化 GC 回收 与 var let 和 const 1/2> ( https://ufqi.com/blog/javascript-gc-var-let-const/ ) , 继续谈 JavaScript 的程序优化内存占用方面的改进。之前说内存使用效率的提升大致有三个方向:
1. 变量声明的关键字的选择 var let 和 const 各个不同;
2. 注意 DOM 对象的引用和显式重置为空释放对象;
3. 注意 定时器 对象的清理,需要手工显式清理掉。
之前一篇已经说了 2, 3 ,这次来说第 1 点,也是最重要的一点,所以单开了一篇来梳理关于 JavaScript 变量声明关键词对程序运行和内存占用的影响。写作过程中这次使用的是 AI 助手 字节跳动 的 豆包AI .
JavaScript 中 const、var 和 let 这三个变量声明关键字的核心区别分别是它们的作用域、变量提升、可赋值性等这些特性,这是 JavaScript 基础中最关键的知识点之一,它将直接影响代码的可读性和稳定性。
一、核心区别总览
先通过表格快速掌握三者的核心差异,再逐个拆解细节:
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 / 全局作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 提升且初始化 undefined | 提升但未初始化(暂时性死区) | 提升但未初始化(暂时性死区) |
| 可重复声明 | ✅ 允许 | ❌ 不允许 | ❌ 不允许 |
| 可重新赋值 | ✅ 允许 | ✅ 允许 | ❌ 不允许(引用类型内容可改) |
| 声明时必须赋初始值 | ❌ 可选 | ❌ 可选 | ✅ 必须 |
| 全局作用域声明 | 挂载到 window 对象 | 不挂载到 window | 不挂载到 window |
二、逐个拆解 + 代码示例
1. var:ES5 关键字,函数作用域(易踩坑)
var 是 ES5 ( ECMAScript 2009 ) 唯一的变量声明方式,核心问题是无块级作用域和提升行为不直观,也是 ES6 ( ECMAScript 2015 ) 推出 let/const 的主要原因。
// 1. 函数作用域:if/for 等块无法限制 var 作用域
if (true) {
var a = 10;
}
console.log(a); // 10(if 块外仍能访问,因为 var 是函数作用域)
// 2. 变量提升:声明提升到作用域顶部,且初始化为 undefined
console.log(b); // undefined(不会报错,提升后默认值)
var b = 20;
// 3. 可重复声明 + 可重新赋值
var c = 30;
var c = 40; // 重复声明不报错
c = 50; // 重新赋值不报错
console.log(c); // 50
// 4. 全局声明挂载到 window
var d = 100;
console.log(window.d); // 100(全局 var 变量会成为 window 属性)
2. let:ES6 关键字,块级作用域(替代 var 的首选)
let 解决了 var 的作用域和提升坑,是声明 “可变变量” 的首选。
// 1. 块级作用域:if/for 等块可限制作用域
if (true) {
let a = 10;
}
console.log(a); // ReferenceError: a is not defined(块外无法访问)
// 2. 暂时性死区(TDZ):提升但未初始化,声明前访问报错
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;
// 3. 不可重复声明,可重新赋值
let c = 30;
// let c = 40; // SyntaxError: Identifier 'c' has already been declared
c = 40; // 重新赋值正常
console.log(c); // 40
// 4. 全局声明不挂载到 window
let d = 100;
console.log(window.d); // undefined
3. const:ES6 关键字,块级作用域 + 只读(声明常量)
const 核心是声明 “只读引用”,不是 “不可变值”—— 基本类型(数字、字符串、布尔)不可改,引用类型(对象、数组)的内容可改,但不能重新赋值引用。
// 1. 声明时必须赋初始值
// const a; // SyntaxError: Missing initializer in const declaration
const a = 10;
// 2. 不可重新赋值(基本类型)
// a = 20; // TypeError: Assignment to constant variable.
// 3. 引用类型(对象/数组):内容可改,引用不可改
const obj = { name: "张三" };
obj.name = "李四"; // 正常(修改对象内容,引用未变)
console.log(obj); // { name: '李四' }
// obj = { age: 20 }; // 报错(重新赋值引用)
const arr = [1, 2, 3];
arr.push(4); // 正常(修改数组内容)
console.log(arr); // [1,2,3,4]
// 4. 块级作用域 + 暂时性死区(和 let 一致)
if (true) {
const b = 20;
console.log(b); // 20
}
// console.log(b); // ReferenceError
- 优先用 const:只要变量不需要重新赋值(90% 场景),就用
const,让代码语义更清晰,减少意外赋值。 - 其次用 let:只有变量需要重新赋值时(如循环变量、状态变量),才用
let。 - 彻底抛弃 var:
var的作用域和提升问题容易导致隐蔽 bug,ES6 环境下完全可以用let/const替代。
三、最佳实践建议
总结
var是 ES5 关键字,函数作用域、可重复声明、提升后初始化undefined,已不推荐使用;let是块级作用域,不可重复声明、有暂时性死区,适合声明需要重新赋值的变量;const是块级作用域,不可重复声明、声明时必须赋值、不可重新赋值(但引用类型内容可改),适合声明常量 / 只读引用。
记住核心原则:能用 const 就不用 let,能用 let/const 就不用 var,这是现代 JavaScript 开发的通用规范。
四、挂载变量到 window 全局
既然推荐优先使用 let 和 const ,那就立即采用,可是问题也来了。这两个关键词声明的变量默认都是块级作用域,不能曝露到 window 页面全局,如希望能够全局访问到某个 let 或者 const 声明的变量,该如何操作?
const/let 声明的变量默认是块级作用域,且不会自动成为 window 的属性(这也是它们和 var 的关键区别之一)。要实现全局访问,核心是手动给 window 全局对象添加属性,将 const 变量的值赋给这个属性;如果希望 window 上的 “全局常量” 也具备 const 的 “不可重新赋值” 特性,可通过 Object.defineProperty 限制属性的可写性。
具体实现方法
方法 1:基础挂载(简单快捷,window 属性可修改)
先声明 const 变量,再将其值赋给 window 的属性,即可实现全局访问。
// 1. 声明块级作用域的 const 变量
const API_BASE_URL = 'https://api.example.com';
const MAX_SIZE = 100;
// 2. 挂载到 window 全局对象
window.API_BASE_URL = API_BASE_URL;
window.MAX_SIZE = MAX_SIZE;
// 3. 任意作用域均可访问
function test() {
console.log(window.API_BASE_URL); // 输出:https://api.example.com
console.log(MAX_SIZE); // 若 test 函数所在作用域无 MAX_SIZE,直接访问会报错,需用 window.MAX_SIZE
}
test();
// 注意:window 上的属性默认可修改(和 const 原变量不同)
window.MAX_SIZE = 200;
console.log(window.MAX_SIZE); // 输出:200(可修改)
API_BASE_URL = 'xxx'; // 报错:const 原变量不可重新赋值
方法 2:严格挂载(模拟 const 特性,window 属性不可修改)
如果希望 window 上的全局 “常量” 也像 const 一样不可重新赋值、不可删除,可使用 Object.defineProperty 配置属性描述符,这是更符合 const 语义的做法。
// 定义全局常量并挂载到 window,且不可修改、不可删除
Object.defineProperty(window, 'PI', {
value: 3.1415926, // 常量值
writable: false, // 核心:设置为 false,禁止重新赋值(模拟 const)
enumerable: true, // 可枚举(能被 for...in 遍历到)
configurable: false // 不可配置(禁止删除属性、修改属性描述符)
});
// 全局访问
console.log(window.PI); // 输出:3.1415926
console.log(PI); // 全局作用域下可直接访问(无需 window.)
// 尝试修改/删除,验证 const 特性
window.PI = 3; // 无报错,但赋值无效(严格模式下会报错)
console.log(window.PI); // 仍输出:3.1415926
delete window.PI; // 无法删除
console.log(window.PI); // 仍输出:3.1415926
// 严格模式下修改会直接报错(推荐开启严格模式)
'use strict';
window.PI = 3; // 报错:TypeError: Cannot assign to read only property 'PI' of object '#<Window>'
- 全局常量建议用大写字母 + 下划线命名(如
API_BASE_URL),区分普通变量,提高可读性。
总结
- 核心方案:通过
window.xxx = const变量或Object.defineProperty将 const 变量挂载到全局对象,实现跨作用域访问; - 如需模拟
const的 “不可重新赋值” 特性,必须用Object.defineProperty设置writable: false; - 原
const变量的块级作用域特性不影响 window 属性的全局访问,全局访问时建议显式写window.xxx(避免作用域冲突)。
这种方式既保留了 const 的语义(不可随意修改),又实现了变量的全局共享,是前端项目中管理全局常量的常用最佳实践。
经过前述三项重要修改,在 UfqiFina 有福金融 的项目具体实践中,我们观测到的单页面应用内存占用情况:
峰值 3000MB~5000MB ↓→ 500MB~1500MB 。
运行性能和内存优化大约有 2 倍 至 10 倍 的提升。










