实现ES6的const

通过Object.defineProperty模拟实现ES6中const关键字的常量定义功能

问题

在ES6之前,JavaScript没有原生的常量定义方式。本题要求手动实现一个类似ES6 const 的功能,使得定义的变量:

  1. 不能被重新赋值
  2. 必须在声明时初始化
  3. 具有块级作用域(可选实现)

解答

/**
 * 实现const关键字功能
 * @param {string} key - 常量名
 * @param {*} value - 常量值
 * @param {object} target - 目标对象,默认为window
 */
function _const(key, value, target = window) {
  // 检查是否已经定义过该常量
  if (target.hasOwnProperty(key)) {
    throw new TypeError(`Identifier '${key}' has already been declared`);
  }
  
  // 检查是否提供了初始值
  if (value === undefined) {
    throw new SyntaxError('Missing initializer in const declaration');
  }
  
  // 使用Object.defineProperty定义不可修改的属性
  Object.defineProperty(target, key, {
    value: value,
    writable: false,      // 不可写
    enumerable: true,     // 可枚举
    configurable: false   // 不可配置(不可删除)
  });
}

// 方案二:使用Proxy实现更严格的拦截
function createConstContext() {
  const constMap = new Map();
  
  return {
    define(key, value) {
      if (constMap.has(key)) {
        throw new TypeError(`Identifier '${key}' has already been declared`);
      }
      if (value === undefined) {
        throw new SyntaxError('Missing initializer in const declaration');
      }
      constMap.set(key, value);
    },
    
    get(key) {
      if (!constMap.has(key)) {
        throw new ReferenceError(`${key} is not defined`);
      }
      return constMap.get(key);
    },
    
    set(key, value) {
      if (constMap.has(key)) {
        throw new TypeError('Assignment to constant variable');
      }
      throw new ReferenceError(`${key} is not defined`);
    }
  };
}

使用示例

// 示例1:基础使用
_const('PI', 3.14159);
console.log(PI); // 3.14159

// 尝试重新赋值(会静默失败或在严格模式下报错)
PI = 3.14;
console.log(PI); // 3.14159(值未改变)

// 示例2:重复声明会报错
try {
  _const('PI', 3.14);
} catch (e) {
  console.error(e.message); // Identifier 'PI' has already been declared
}

// 示例3:未初始化会报错
try {
  _const('MAX_SIZE');
} catch (e) {
  console.error(e.message); // Missing initializer in const declaration
}

// 示例4:使用Proxy方案
const constCtx = createConstContext();

constCtx.define('API_URL', 'https://api.example.com');
console.log(constCtx.get('API_URL')); // https://api.example.com

try {
  constCtx.set('API_URL', 'https://new-api.com');
} catch (e) {
  console.error(e.message); // Assignment to constant variable
}

// 示例5:定义对象常量
_const('CONFIG', { port: 3000, host: 'localhost' });
console.log(CONFIG.port); // 3000

// 注意:对象内部属性仍可修改(与真实const行为一致)
CONFIG.port = 8080;
console.log(CONFIG.port); // 8080

// 但不能重新赋值整个对象
CONFIG = {}; // 静默失败或报错

关键点

  • Object.defineProperty:API,通过设置 writable: falseconfigurable: false 实现不可修改和不可删除

  • 属性描述符配置

    • writable: false - 防止值被修改
    • configurable: false - 防止属性被删除或重新配置
    • enumerable: true - 保持可枚举性
  • 初始化检查:const必须在声明时赋值,需要检查value是否为undefined

  • 重复声明检查:使用 hasOwnProperty 检查属性是否已存在,防止重复声明

  • 浅层不可变:与真实const一样,只保证变量引用不可变,对象内部属性仍可修改。如需深度冻结,可使用 Object.freeze()

  • 作用域限制:简单实现挂载在window上,实际const具有块级作用域,完整实现需要配合作用域管理

  • Proxy方案优势:可以实现更精确的错误提示和访问控制,更接近真实const的行为