MST

星途 面试题库

面试题:JavaScript中在复杂场景下实现对象可扩展能力的动态切换

在一个大型JavaScript项目中,有一个核心对象`CoreObject`,它在不同的业务模块下需要具备不同的可扩展能力。例如,在模块A中,`CoreObject`的所有属性都必须是只读且不可扩展新属性;在模块B中,`CoreObject`需要能够动态添加新属性,并且新属性可以根据特定条件进行读写控制。请设计一套完整的方案来实现这种动态切换的对象可扩展能力,包括涉及到的方法、事件监听以及数据结构,并阐述该方案在性能和维护性方面的考虑。
40.0万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

实现方案

  1. 数据结构

    • 为了实现不同模块下CoreObject不同的可扩展能力,可以在CoreObject上添加一个元数据属性来标识当前所处的模块环境。例如:
    const CoreObject = {
        module: null,
        // 其他初始属性
    };
    
  2. 方法

    • 设置模块方法
      function setModule(CoreObject, moduleName) {
          CoreObject.module = moduleName;
      }
      
    • 模块A相关方法
      • CoreObject设置为只读且不可扩展新属性。
      function makeReadOnlyAndNonExtensible(CoreObject) {
          Object.freeze(CoreObject);
          Object.seal(CoreObject);
      }
      
    • 模块B相关方法
      • 添加新属性并实现特定条件下的读写控制。
      function addPropertyWithControl(CoreObject, propertyName, value, readCondition, writeCondition) {
          const propertyDescriptor = {
              get: function() {
                  if (readCondition()) {
                      return value;
                  }
                  return undefined;
              },
              set: function(newValue) {
                  if (writeCondition()) {
                      value = newValue;
                  }
              },
              enumerable: true
          };
          Object.defineProperty(CoreObject, propertyName, propertyDescriptor);
      }
      
  3. 事件监听(可使用自定义事件模拟)

    • 在JavaScript中,可以通过CustomEvent来模拟事件监听。例如,当模块切换时触发一个事件。
    function onModuleChange(CoreObject, callback) {
        const eventName ='moduleChange';
        if (!CoreObject.__eventListeners) {
            CoreObject.__eventListeners = {};
        }
        if (!CoreObject.__eventListeners[eventName]) {
            CoreObject.__eventListeners[eventName] = [];
        }
        CoreObject.__eventListeners[eventName].push(callback);
        function triggerEvent() {
            const event = new CustomEvent(eventName);
            CoreObject.__eventListeners[eventName].forEach(listener => listener(event));
        }
        return triggerEvent;
    }
    
    • 使用示例:
    const triggerModuleChange = onModuleChange(CoreObject, function(event) {
        if (CoreObject.module === 'A') {
            makeReadOnlyAndNonExtensible(CoreObject);
        } else if (CoreObject.module === 'B') {
            // 可以在这里进行模块B的初始化操作,如设置读写条件等
        }
    });
    setModule(CoreObject, 'A');
    triggerModuleChange();
    

性能和维护性方面的考虑

  1. 性能

    • 模块切换:模块切换时,设置模块方法setModule和触发事件triggerModuleChange的操作复杂度都是常数级别的,不会随着CoreObject的属性数量增加而显著增加。
    • 属性操作:在模块B中,addPropertyWithControl使用Object.defineProperty定义属性,虽然相比于直接赋值会有一定性能开销,但在大型项目中,属性添加操作通常不会非常频繁,且这种开销在可接受范围内。而模块A中的Object.freezeObject.seal操作在首次设置时开销相对固定,对后续操作性能影响较小,因为禁止了属性的修改和扩展。
  2. 维护性

    • 模块化:通过将不同模块的行为封装成独立的函数(如makeReadOnlyAndNonExtensibleaddPropertyWithControl),使得代码结构清晰,易于理解和维护。如果未来需要修改某个模块下CoreObject的行为,只需要修改对应的函数即可。
    • 事件驱动:使用自定义事件监听机制,如onModuleChange,使得模块切换的逻辑可以集中处理,并且可以方便地添加更多的回调函数来处理不同的业务逻辑,增强了代码的可扩展性。同时,通过元数据CoreObject.module来标识模块环境,使得代码在不同模块间切换的逻辑更加直观。