在 JavaScript 中,没有直接的内置语法来声明一个可以不初始化但只能赋值一次的变量。const必须初始化,let可以多次赋值。
不过,有几种方法可以模拟这种需求:
1. 使用 let并添加编程约束
最简单的方法是通过代码规范来约定:
let myVariable; // 声明时不赋值
// 约定:只能赋值一次
function init() {
if (myVariable === undefined) {
myVariable = '只赋值一次的值';
} else {
throw new Error('此变量已赋值,不能再次赋值');
}
}
2. 使用 getter 和 setter 封装
创建一个包装对象来控制赋值:
function createAssignOnceVariable() {
let value;
let assigned = false;
return {
set: function(newValue) {
if (assigned) {
throw new Error('此变量只能赋值一次');
}
value = newValue;
assigned = true;
},
get: function() {
if (!assigned) {
throw new Error('此变量尚未赋值');
}
return value;
},
isAssigned: function() {
return assigned;
}
};
}
// 使用示例
const config = createAssignOnceVariable();
console.log(config.isAssigned()); // false
config.set('初始值'); // ✅
console.log(config.get()); // '初始值'
config.set('新值'); // ❌ 抛出错误
3. 使用类来实现
class AssignOnce {
#value;
#assigned = false;
set(value) {
if (this.#assigned) {
throw new Error('只能赋值一次');
}
this.#value = value;
this.#assigned = true;
// 可选的:设置后冻结对象
Object.freeze(this);
}
get() {
if (!this.#assigned) {
throw new Error('尚未赋值');
}
return this.#value;
}
get isAssigned() {
return this.#assigned;
}
}
// 使用示例
const apiKey = new AssignOnce();
console.log(apiKey.isAssigned); // false
apiKey.set('abc123'); // ✅
console.log(apiKey.get()); // 'abc123'
apiKey.set('xyz789'); // ❌ 抛出错误
4. 使用 Proxy 代理
function createAssignOnceProxy(initialValue) {
let value = initialValue;
let assigned = initialValue !== undefined;
return new Proxy({}, {
get(target, prop) {
if (prop === 'value') {
if (!assigned) throw new Error('尚未赋值');
return value;
}
if (prop === 'isAssigned') return assigned;
return undefined;
},
set(target, prop, newValue) {
if (prop === 'value') {
if (assigned) throw new Error('只能赋值一次');
value = newValue;
assigned = true;
return true;
}
return false;
}
});
}
// 使用示例
const token = createAssignOnceProxy(); // 可以不初始化
console.log(token.isAssigned); // false
token.value = 'first-token'; // ✅
console.log(token.value); // 'first-token'
token.value = 'second-token'; // ❌ 静默失败(严格模式下会报错)
5. TypeScript 中的解决方案
如果你使用 TypeScript,可以通过类型系统获得更好的支持:
// TypeScript: 使用 readonly 属性
class Configuration {
private _apiUrl?: string;
get apiUrl(): string {
if (!this._apiUrl) {
throw new Error('API URL 未设置');
}
return this._apiUrl;
}
set apiUrl(value: string) {
if (this._apiUrl) {
throw new Error('API URL 只能设置一次');
}
this._apiUrl = value;
}
}
实际建议
在实际开发中,最常用的模式是:
- 尽可能在声明时赋值,使用
const - 如果需要延迟初始化,用
let但约定只赋值一次 - 复杂的场景,使用类或工厂函数封装
// 实用模式:初始化函数
function initializeApp() {
let config; // 声明但不赋值
return {
init: function(settings) {
if (config) {
throw new Error('应用已初始化');
}
config = Object.freeze({ ...settings });
},
getConfig: function() {
if (!config) {
throw new Error('应用未初始化');
}
return config;
}
};
}
const app = initializeApp();
app.init({ apiUrl: '...' }); // ✅
// app.init({ ... }); // ❌ 第二次会报错