const 的不可变性边界:引用不变 vs 值不变
理解 const 的真正含义
在 JavaScript 中,const 关键字用于声明常量,但它所提供的"不可变性"常常被误解。实际上,const 只保证变量的绑定是不可变的,而不是变量所引用的值不可变。
const 的基本行为
javascript
// const 声明后不能重新赋值
const PI = 3.14159;
PI = 3.14; // TypeError: Assignment to constant variable.
// const 必须在声明时初始化
const name; // SyntaxError: Missing initializer in const declaration引用不变 vs 值不变
javascript
// 对于基本类型,const 确实提供了值不变性
const number = 42;
const string = "Hello";
const boolean = true;
number = 43; // TypeError
string = "World"; // TypeError
boolean = false; // TypeError
// 对于对象和数组,const 只保证引用不变
const obj = { name: "John" };
const arr = [1, 2, 3];
// 可以修改对象和数组的内容
obj.name = "Jane";
obj.age = 30;
arr.push(4);
arr[0] = 10;
console.log(obj); // { name: "Jane", age: 30 }
console.log(arr); // [10, 2, 3, 4]
// 但不能重新赋值整个对象或数组
obj = { name: "Bob" }; // TypeError
arr = [5, 6, 7]; // TypeError深入理解对象和数组的 const 声明
对象属性的修改
javascript
const user = {
name: "Alice",
profile: {
age: 25,
email: "alice@example.com"
}
};
// 可以修改属性
user.name = "Bob";
user.profile.age = 26;
// 可以添加新属性
user.location = "New York";
// 可以删除属性
delete user.profile.email;
console.log(user);
// {
// name: "Bob",
// profile: { age: 26 },
// location: "New York"
// }数组元素的修改
javascript
const numbers = [1, 2, 3, 4, 5];
// 可以修改元素
numbers[0] = 10;
// 可以添加元素
numbers.push(6);
numbers.unshift(0);
// 可以删除元素
numbers.pop();
numbers.shift();
// 可以改变数组长度
numbers.length = 3;
console.log(numbers); // [0, 10, 2]创建真正不可变的对象和数组
使用 Object.freeze()
javascript
const frozenObj = Object.freeze({
name: "John",
age: 30
});
// 无法修改属性(在严格模式下会抛出错误)
frozenObj.name = "Jane"; // 静默失败(非严格模式)
frozenObj.newProp = "test"; // 静默失败(非严格模式)
delete frozenObj.age; // 静默失败(非严格模式)
console.log(frozenObj); // { name: "John", age: 30 }
// 注意:Object.freeze() 只是浅冻结
const deepObj = Object.freeze({
name: "John",
address: {
city: "New York"
}
});
deepObj.address.city = "Boston"; // 可以修改嵌套对象
console.log(deepObj.address.city); // "Boston"深度冻结对象
javascript
function deepFreeze(obj) {
// 获取对象的所有属性名
Object.getOwnPropertyNames(obj).forEach(function(prop) {
// 如果属性值是对象,递归冻结
if (obj[prop] !== null && typeof obj[prop] === "object") {
deepFreeze(obj[prop]);
}
});
return Object.freeze(obj);
}
const deepFrozenObj = deepFreeze({
name: "John",
address: {
city: "New York"
}
});
// 现在无法修改嵌套对象
deepFrozenObj.address.city = "Boston"; // 静默失败
console.log(deepFrozenObj.address.city); // "New York"使用不可变数据结构库
javascript
// 使用 Immutable.js 示例
// const { Map } = require('immutable');
//
// const immutableMap = Map({
// name: "John",
// age: 30
// });
//
// const updatedMap = immutableMap.set('name', 'Jane');
//
// console.log(immutableMap.get('name')); // "John"
// console.log(updatedMap.get('name')); // "Jane"const 与 let 的选择
何时使用 const
javascript
// 1. 基本类型的常量
const PI = 3.14159;
const MAX_SIZE = 100;
// 2. 对象和数组的引用(当你不打算重新赋值整个对象/数组时)
const users = [];
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
// 3. 函数声明
const add = (a, b) => a + b;
const fetchData = async () => {
const response = await fetch('/api/data');
return response.json();
};何时使用 let
javascript
// 1. 需要重新赋值的变量
let counter = 0;
counter++; // 可以修改
// 2. 循环变量
for (let i = 0; i < 10; i++) {
// 循环逻辑
}
// 3. 条件赋值
let result;
if (condition) {
result = "A";
} else {
result = "B";
}实际应用场景
React 中的组件状态
javascript
// 在 React 函数组件中
function UserProfile({ userId }) {
// 使用 const 声明引用
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 使用 setUser 修改状态,而不是重新赋值 user 变量
fetchUser(userId).then(userData => {
setUser(userData);
setLoading(false);
});
}, [userId]);
// user 变量本身不会重新赋值,但其引用的对象会改变
return (
<div>
{loading ? "Loading..." : user.name}
</div>
);
}配置对象管理
javascript
// 应用配置
const appConfig = {
version: "1.0.0",
features: {
darkMode: true,
notifications: false
}
};
// 可以修改配置项
appConfig.features.darkMode = false;
appConfig.features.notifications = true;
// 但不能替换整个配置对象
// appConfig = {}; // TypeError最佳实践
1. 默认使用 const
javascript
// 推荐:默认使用 const
const processItems = (items) => {
const processed = [];
for (const item of items) {
const result = transformItem(item);
processed.push(result);
}
return processed;
};2. 明确意图
javascript
// 清晰地表达变量不会被重新赋值的意图
const DATABASE_URL = process.env.DATABASE_URL;
const API_TIMEOUT = 5000;3. 避免误解不可变性
javascript
// 当需要真正不可变的数据时,明确说明
const CONFIG = Object.freeze({
API_ENDPOINT: '/api',
MAX_RETRIES: 3
});总结
const 关键字提供的是绑定不变性而不是值不变性。对于基本类型,这确实意味着值不可变;但对于对象和数组,它只防止重新赋值整个对象或数组,而不防止修改其内容。理解这一区别对于编写正确的 JavaScript 代码至关重要。在需要真正不可变的数据时,应使用 Object.freeze() 或专门的不可变数据结构库。