MST

星途 面试题库

面试题:JavaScript访问器属性在复杂场景下的优化与应用

假设有一个大型JavaScript应用程序,其中有多个类相互依赖,并且广泛使用了访问器属性来控制数据的访问和修改。现在发现性能问题,尤其是在频繁读写访问器属性时。请描述你会采取哪些优化策略,从设计模式、代码结构、缓存机制等方面进行阐述,并给出相关的示例代码片段来解决这个性能问题。
40.9万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

设计模式优化

  1. 减少不必要的类依赖
    • 策略:通过使用依赖注入模式,将依赖关系从类内部解耦。这样可以避免在类实例化时过多的初始化操作,提高性能。例如,原本类A直接在内部实例化类B,现在通过构造函数传入B的实例。
    • 示例代码
// 原始代码,类A直接依赖类B
class B {
    constructor() {
        this.data = 'default data in B';
    }
}
class A {
    constructor() {
        this.b = new B();
    }
    getBData() {
        return this.b.data;
    }
}

// 使用依赖注入优化
class AOptimized {
    constructor(bInstance) {
        this.b = bInstance;
    }
    getBData() {
        return this.b.data;
    }
}

// 使用
const b = new B();
const aOptimized = new AOptimized(b);
console.log(aOptimized.getBData());
  1. 使用代理模式
    • 策略:对于频繁访问的对象,可以使用代理模式来控制访问。代理可以在访问实际对象前进行一些预处理,如缓存数据等,减少对原始对象访问器属性的直接访问。
    • 示例代码
class DataObject {
    constructor() {
        this._data = { key: 'value' };
    }
    get data() {
        console.log('Accessing data...');
        return this._data;
    }
    set data(newData) {
        console.log('Setting data...');
        this._data = newData;
    }
}

const dataObject = new DataObject();
const dataProxy = new Proxy(dataObject, {
    get(target, property) {
        if (property === 'data' && target.__cachedData) {
            return target.__cachedData;
        }
        const result = target[property];
        if (property === 'data') {
            target.__cachedData = result;
        }
        return result;
    },
    set(target, property, value) {
        if (property === 'data') {
            target.__cachedData = value;
        }
        return Reflect.set(target, property, value);
    }
});

console.log(dataProxy.data);
console.log(dataProxy.data); // 第二次访问直接从缓存获取
dataProxy.data = { newKey: 'newValue' };
console.log(dataProxy.data);

代码结构优化

  1. 缓存频繁访问的数据
    • 策略:在类内部设置一个缓存变量,当第一次访问访问器属性时,将结果缓存起来,后续访问直接返回缓存值。
    • 示例代码
class MyClass {
    constructor() {
        this._data = [1, 2, 3, 4, 5];
        this._cachedSum = null;
    }
    get sum() {
        if (this._cachedSum === null) {
            this._cachedSum = this._data.reduce((acc, num) => acc + num, 0);
        }
        return this._cachedSum;
    }
}

const myClass = new MyClass();
console.log(myClass.sum);
console.log(myClass.sum); // 直接返回缓存值
  1. 避免过度封装
    • 策略:如果某些访问器属性只是简单的读取或设置操作,可以考虑直接暴露属性,而不是使用访问器。这样可以减少函数调用的开销。例如,原本使用访问器获取和设置一个简单的数值属性。
    • 示例代码
// 原始过度封装代码
class OldClass {
    constructor() {
        this._number = 0;
    }
    get number() {
        return this._number;
    }
    set number(newNumber) {
        this._number = newNumber;
    }
}

// 优化后
class NewClass {
    constructor() {
        this.number = 0;
    }
}

const newObj = new NewClass();
console.log(newObj.number);
newObj.number = 10;
console.log(newObj.number);

缓存机制优化

  1. 使用Memoization(记忆化)
    • 策略:对于访问器属性的计算结果进行记忆化,即缓存函数的计算结果。如果再次调用相同参数的函数(在访问器属性的场景下,通常无参数,但概念类似),直接返回缓存结果。
    • 示例代码
function memoize(func) {
    const cache = new Map();
    return function() {
        const key = JSON.stringify(arguments);
        if (cache.has(key)) {
            return cache.get(key);
        }
        const result = func.apply(this, arguments);
        cache.set(key, result);
        return result;
    };
}

class ComplexCalculationClass {
    constructor() {
        this._data = [1, 2, 3, 4, 5];
    }
    calculateComplexValue() {
        let result = 0;
        for (let i = 0; i < this._data.length; i++) {
            result += Math.pow(this._data[i], 2);
        }
        return result;
    }
    get complexValue() {
        return memoize(this.calculateComplexValue.bind(this))();
    }
}

const complexObj = new ComplexCalculationClass();
console.log(complexObj.complexValue);
console.log(complexObj.complexValue); // 直接返回缓存结果