一、const基本概念
1. const是什么?
-
const用于声明常量(不可重新赋值的变量) -
具有块级作用域(与let相同)
-
存在暂时性死区(TDZ)
-
必须在声明时初始化
javascript
// 基本用法
const PI = 3.14159;
console.log(PI); // 3.14159
// 必须初始化
const MAX_VALUE; // SyntaxError: Missing initializer in const declaration
// 不能重新赋值
PI = 3.14; // TypeError: Assignment to constant variable
2. const vs let vs var
javascript
// 作用域对比
{
var varVariable = 'var';
let letVariable = 'let';
const constVariable = 'const';
}
console.log(varVariable); // 'var' - 函数作用域
console.log(letVariable); // ReferenceError - 块级作用域
console.log(constVariable); // ReferenceError - 块级作用域
// 重复声明对比
var a = 1;
var a = 2; // 允许
let b = 1;
let b = 2; // SyntaxError: Identifier 'b' has already been declared
const c = 1;
const c = 2; // SyntaxError: Identifier 'c' has already been declared
// 变量提升对比
console.log(x); // undefined - var会提升
var x = 10;
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 20;
console.log(z); // ReferenceError: Cannot access 'z' before initialization
const z = 30;
二、const的特殊行为
1. const与不可变性
javascript
// const保证的是绑定(变量名与值的引用)不可变,不是值本身不可变
// 基本类型 - 值不可变
const num = 42;
num = 100; // TypeError: Assignment to constant variable
const str = 'hello';
str[0] = 'H'; // 静默失败,字符串不可变
console.log(str); // 'hello'
// 引用类型 - 引用不可变,但内容可变
const obj = { name: 'Alice' };
obj = {}; // TypeError: 不能重新赋值(改变引用)
obj.name = 'Bob'; // 允许:修改对象内容
obj.age = 25; // 允许:添加新属性
console.log(obj); // {name: 'Bob', age: 25}
const arr = [1, 2, 3];
arr = []; // TypeError: 不能重新赋值
arr.push(4); // 允许:修改数组内容
arr[0] = 100; // 允许:修改元素
console.log(arr); // [100, 2, 3, 4]
// 函数
const func = () => console.log('hello');
func = () => console.log('world'); // TypeError
func.prop = 'value'; // 允许:添加属性
2. 实现真正的不可变对象
javascript
// 方法1:Object.freeze() - 浅冻结
const obj = Object.freeze({ name: 'Alice', address: { city: 'Beijing' } });
obj.name = 'Bob'; // 静默失败(严格模式下报错)
obj.age = 25; // 静默失败(严格模式下报错)
// 但是嵌套对象仍然可变
obj.address.city = 'Shanghai'; // 允许!
console.log(obj.address.city); // 'Shanghai'
// 方法2:深冻结
function deepFreeze(obj) {
Object.keys(obj).forEach(key => {
if (obj[key] && typeof obj[key] === 'object') {
deepFreeze(obj[key]);
}
});
return Object.freeze(obj);
}
const deepFrozen = deepFreeze({
name: 'Alice',
address: { city: 'Beijing' }
});
deepFrozen.address.city = 'Shanghai'; // 静默失败
console.log(deepFrozen.address.city); // 'Beijing'
// 方法3:使用Object.seal() - 密封
const sealed = Object.seal({ name: 'Alice' });
sealed.name = 'Bob'; // 允许:修改现有属性
sealed.age = 25; // 静默失败:不能添加新属性
delete sealed.name; // 静默失败:不能删除属性
三、const的实用场景
1. 配置和常量
javascript
// 应用配置
const CONFIG = {
API_URL: 'https://api.example.com',
TIMEOUT: 5000,
RETRY_ATTEMPTS: 3,
FEATURES: {
LOGGING: true,
CACHE: false,
ANALYTICS: true
}
};
// 使用Object.freeze确保配置不被修改
Object.freeze(CONFIG);
Object.freeze(CONFIG.FEATURES);
// 数学常量
const MATH_CONSTANTS = Object.freeze({
PI: 3.141592653589793,
E: 2.718281828459045,
PHI: 1.618033988749895,
SQRT2: 1.4142135623730951
});
// 状态码
const HTTP_STATUS = Object.freeze({
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
INTERNAL_SERVER_ERROR: 500
});
2. 函数式编程
javascript
// 纯函数中使用const
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
// 高阶函数
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
// 柯里化
const curry = (fn) => {
const arity = fn.length;
return function curried(...args) {
if (args.length >= arity) {
return fn.apply(this, args);
}
return (...moreArgs) => curried.apply(this, args.concat(moreArgs));
};
};
// 使用
const addCurried = curry((a, b) => a + b);
const add5 = addCurried(5);
console.log(add5(10)); // 15
3. 模块导出
javascript
// module.js
export const VERSION = '1.0.0';
export const API_ENDPOINTS = Object.freeze({
USERS: '/api/users',
POSTS: '/api/posts',
COMMENTS: '/api/comments'
});
export const createService = (config) => {
const baseUrl = config.baseUrl;
return {
getUsers: () => fetch(`${baseUrl}${API_ENDPOINTS.USERS}`),
getPosts: () => fetch(`${baseUrl}${API_ENDPOINTS.POSTS}`)
};
};
// main.js
import { VERSION, API_ENDPOINTS, createService } from './module.js';
console.log(VERSION); // '1.0.0'
console.log(API_ENDPOINTS.USERS); // '/api/users'
const service = createService({ baseUrl: 'https://example.com' });
四、const与循环
1. for循环中的const
javascript
// 传统for循环 - 不能使用const(因为i需要重新赋值)
for (let i = 0; i < 5; i++) { // 正确:使用let
console.log(i);
}
for (const i = 0; i < 5; i++) { // TypeError: Assignment to constant variable
console.log(i);
}
// for...in 循环 - 可以使用const
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
console.log(key, obj[key]);
// key = 'newKey'; // TypeError: 不能重新赋值
}
// for...of 循环 - 可以使用const
const arr = [10, 20, 30];
for (const value of arr) {
console.log(value);
// value = 100; // TypeError: 不能重新赋值
}
// 数组forEach - 可以使用const
arr.forEach((value, index) => {
const squared = value * value; // 每次迭代创建新的const
console.log(squared);
});
2. 迭代中的const模式
javascript
// 每次迭代都创建新的const绑定
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
// 每次迭代都是独立的const
for (const user of users) {
// user是const,不能重新赋值,但可以修改其属性
// user = {}; // TypeError
user.active = true; // 允许:修改对象内容
console.log(user);
}
// map方法中使用const
const userNames = users.map(user => {
const { id, name } = user; // 解构赋值创建const
return { id, name };
});
五、const的性能优化
1. const vs let的性能
javascript
// 现代JavaScript引擎对const有优化
// const告诉引擎这个变量不会重新赋值,引擎可以做更多优化
// 示例:循环中使用const
const data = new Array(1000000).fill(0);
// 使用const(在for...of中)
function sumWithConst(data) {
let total = 0;
for (const value of data) {
total += value;
}
return total;
}
// 使用let
function sumWithLet(data) {
let total = 0;
for (let i = 0; i < data.length; i++) {
total += data[i];
}
return total;
}
// 实际上,现代引擎优化得很好,差异很小
// 但使用const可以提高代码可读性,表明变量的意图
2. 内存管理
javascript
// const有助于避免意外的重新赋值,减少bug
function processItems(items) {
// 使用const声明不会改变的变量
const processedItems = items.map(item => ({
...item,
processed: true
}));
const validItems = processedItems.filter(item => item.isValid);
const totalCount = validItems.length;
// 这些变量都不会被重新赋值,使用const很安全
return { processedItems, validItems, totalCount };
}
// 如果使用let,后续可能意外修改
function buggyFunction(items) {
let result = items.map(processItem);
// ... 很多代码 ...
result = {}; // 意外的重新赋值!
return result;
}
六、const的高级用法
1. const与解构赋值
javascript
// 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [3, 4, 5]
// 对象解构
const person = { name: 'Alice', age: 30, city: 'Beijing' };
const { name, age } = person;
console.log(name, age); // Alice 30
// 重命名
const { name: personName, age: personAge } = person;
// 默认值
const { name: userName = 'Anonymous', country = 'China' } = person;
console.log(userName, country); // Alice China
// 嵌套解构
const company = {
name: 'Tech Corp',
address: {
city: 'Shanghai',
street: 'Main St'
}
};
const {
name: companyName,
address: {
city: companyCity,
street: companyStreet
}
} = company;
console.log(companyName, companyCity); // Tech Corp Shanghai
2. const与模块模式
javascript
// 创建不可变的单例
const Singleton = (function() {
let instance;
function createInstance() {
return {
data: [],
add(item) {
this.data.push(item);
},
get() {
return [...this.data]; // 返回副本,保护数据
}
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
七、const的最佳实践
1. 代码规范建议
javascript
// 默认使用const,只有在需要重新赋值时才使用let
// 好:使用const
const userName = 'Alice';
const userAge = 30;
const isActive = true;
// 只在需要重新赋值时使用let
let counter = 0;
counter = 1; // 需要重新赋值
// 循环变量通常用let
for (let i = 0; i < 10; i++) {
const item = items[i]; // 循环体内使用const
// ...
}
// 避免使用var
// var outdated = '不要使用'; // 避免
2. 命名约定
javascript
// 常规变量 - 驼峰命名
const userName = 'Alice';
const maxRetryCount = 3;
// 全局常量 - 全大写加下划线
const MAX_SIZE = 100;
const API_BASE_URL = 'https://api.example.com';
const DEFAULT_TIMEOUT = 5000;
// 枚举类型 - 使用对象冻结
const COLOR = Object.freeze({
RED: '#FF0000',
GREEN: '#00FF00',
BLUE: '#0000FF'
});
// 配置对象 - 明确不可变
const APP_CONFIG = Object.freeze({
version: '1.0.0',
environment: 'production',
features: Object.freeze({
analytics: true,
caching: false
})
});
3. 常见错误和陷阱
javascript
// 陷阱1:认为const对象完全不可变
const user = { name: 'Alice' };
user.name = 'Bob'; // 允许!只是不能重新赋值user
// 解决方案:使用Object.freeze
const frozenUser = Object.freeze({ name: 'Alice' });
frozenUser.name = 'Bob'; // 静默失败或严格模式报错
// 陷阱2:const在循环中的使用
for (const i = 0; i < 5; i++) { // TypeError
// ...
}
// 正确:使用let
for (let i = 0; i < 5; i++) {
const item = items[i]; // 在循环体内使用const
}
// 陷阱3:const与立即执行函数
const result = (function() {
const value = 42;
return value;
})();
// value在这里不可访问 - 正确
// 陷阱4:const与异步代码
const fetchData = async () => {
const response = await fetch('/api/data'); // 正确
const data = await response.json(); // 正确
return data;
};
// 如果有重新赋值需求,使用let
const processItems = async (items) => {
let result = [];
for (const item of items) {
const processed = await processItem(item);
result = result.concat(processed); // 需要重新赋值
}
return result;
};
八、const在TypeScript中的使用
javascript
// TypeScript中的const有额外类型推断
// 基本类型
const num = 42; // 类型推断为 42(字面量类型),而不是number
const str = 'hello'; // 类型为 "hello",而不是string
// 对象类型
const obj = { x: 10, y: 20 }; // 类型为 { x: number; y: number; }
// 数组类型
const arr = [1, 2, 3]; // 类型为 number[]
// as const断言 - 创建完全不可变的类型
const user = {
name: 'Alice',
age: 30
} as const;
// 类型为 { readonly name: "Alice"; readonly age: 30; }
const numbers = [1, 2, 3] as const;
// 类型为 readonly [1, 2, 3]
// 使用const enum(编译时常量)
const enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
const direction = Direction.Up;
// 编译后:const direction = "UP" /* Direction.Up */;
总结
const的核心要点:
-
绑定不可变:变量名与值/引用的绑定不可变
-
必须初始化:声明时必须赋值
-
块级作用域:只在当前块内有效
-
暂时性死区:声明前不可访问
-
支持闭包:可被内部函数引用
使用建议:
-
默认使用const,需要重新赋值时才用let
-
避免使用var,使用let/const替代
-
使用Object.freeze保护对象内容
-
命名约定:全大写用于全局常量
-
结合解构赋值使用const更安全
适用场景:
-
配置值和常量
-
导入的模块和函数
-
循环中的迭代变量(for...of, for...in)
-
函数式编程中的纯函数
-
任何不需要重新赋值的变量
const是编写可维护、可预测JavaScript代码的重要工具,它通过限制变量的可变性来提高代码质量。