You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript非继承实现类实例方法属性自动代理方案咨询

实现类实例自动代理内部对象的方法与属性(无需继承)

我是JavaScript新手,希望在不使用继承的前提下,让类实例自动代理其内部持有的另一个类实例的方法与属性。比如下面的示例代码中,我期望B的实例能直接调用z实例的方法和访问属性,不用逐个手动封装:

class Z{
    constructor(){}
    a(){console.log('a')}
    get b(){console.log('b')}
    c(){console.log('c')}
}

class B{
    constructor(z){
        this.z = z;
    }
}

let z = new Z();
let b = new B(z);
b.a(); // 当前会报错,期望能直接调用z的a方法
b.b; // 当前会报错,期望能直接访问z的b属性

实际业务场景中,我封装了CodeEditor类来包装CodeMirror实例(CodeMirror实例由库的fromTextArea方法生成,无法直接用extends继承),现在需要手动重写CodeMirror的方法,但项目其他代码会调用CodeMirror的原型方法,希望有通用的自动代理方案,避免逐个重写。


方案1:使用ES6 Proxy实现自动代理

Proxy可以拦截对象的属性访问、方法调用等操作,完美适配这种自动代理需求。在类的构造函数中返回Proxy实例,即可拦截对当前实例的所有属性访问,优先转发给内部目标对象。

适配Z/B场景的示例代码:

class Z{
    constructor(){}
    a(){console.log('a')}
    get b(){console.log('b')}
    c(){console.log('c')}
}

class B{
    constructor(z){
        this.z = z;
        // 返回Proxy实例,拦截属性访问
        return new Proxy(this, {
            get(target, prop) {
                // 优先返回自身属性/方法
                if (prop in target) {
                    return Reflect.get(target, prop);
                }
                // 转发给内部z实例,绑定this避免调用错误
                if (prop in target.z) {
                    const value = Reflect.get(target.z, prop);
                    return typeof value === 'function' ? value.bind(target.z) : value;
                }
                return undefined;
            },
            // 可选:拦截属性设置操作,转发给z实例
            set(target, prop, value) {
                if (prop in target.z) {
                    return Reflect.set(target.z, prop, value);
                }
                return Reflect.set(target, prop, value);
            }
        });
    }
}

let z = new Z();
let b = new B(z);
b.a(); // 输出'a'
b.b; // 输出'b'
b.c(); // 输出'c'

适配CodeEditor场景的修改:

修改CodeEditor构造函数,返回Proxy实例自动代理内部的this.editor:

class CodeEditor{
    constructor(
        container, 
        generic, 
        types, 
        modes, 
        options, 
    ){
        this.generic = generic;
        this.types = types;
        this.modes = modes;

        this.container = container;
        this.textarea = container.querySelector('textarea');
        this.header = this.container.querySelector('.cm-header-bar');
        this.compileBtnWrapper = this.header.querySelector('.cm-code-compile-wrapper');
        this.compileBtn = document.createElement('div');
        this.compileBtn.classList.add('cm-code-compile');
        this.compileBtn.innerText = 'compile';
        this.options = options;

        this.mode = this.textarea.getAttribute('id');
        this.options.mode.name = this.mode;
        this.editor = CodeMirror.fromTextArea(this.textarea, this.options);
        // editor setup
        this.editor.on("gutterClick", function(cm, n) {
            let info = cm.lineInfo(n);
            cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker());
        });
        // compilable
        if(this.mode !== this.generic)this.compilable();
        this.dropdown = dropdown(this.header.querySelector('.dropdown-button'), '预先处理', generic);
        Object.keys(this.modes).forEach(mode=>{
            this.dropdown.addOption(mode, ()=>{
                this.setOption('mode', { name: this.name, globalVars: true });
                this.mode = mode;
                this.editor.refresh();
                play();
                if(mode !== this.generic)this.compilable();
            }, mode === this.mode);
        });

        // 返回Proxy实例,自动代理editor的属性和方法
        return new Proxy(this, {
            get(target, prop) {
                // 优先返回自身属性/方法
                if (prop in target) {
                    return Reflect.get(target, prop);
                }
                // 转发给editor实例,绑定this为editor
                if (prop in target.editor) {
                    const value = Reflect.get(target.editor, prop);
                    return typeof value === 'function' ? value.bind(target.editor) : value;
                }
                return undefined;
            },
            set(target, prop, value) {
                if (prop in target.editor) {
                    return Reflect.set(target.editor, prop, value);
                }
                return Reflect.set(target, prop, value);
            }
        });
    }

    get name(){
        return this.types[this.mode];
    }

    compilable(){
        this.compileBtnWrapper.appendChild(this.compileBtn);
        let temp = {};
        const oxidize = () => {
            this.compileBtn.onclick = () => {
                temp.code = this.getValue();
                temp.mode = this.mode ;
                this.dropdown.onOption(this.generic);
                this.setValue(this.modes[this.mode]());
                this.options.mode.name = this.types[this.generic];
                this.setOption('mode', this.options.mode);
                this.compileBtn.classList.add('active');
                this.compileBtn.innerText = 'restore';
                reduce();
            }
        }
        const reduce = () => {
            this.compileBtn.onclick = () => {
                this.dropdown.onOption(temp.mode);
                this.setValue(temp.code);
                this.options.mode.name = temp.mode;
                this.setOption('mode', this.types[temp.mode]);
                this.compileBtn.classList.remove('active');
                this.compileBtn.innerText = 'compile';
                oxidize();
            }
        }
        oxidize();
    }

    // 移除手动封装的setValue、getValue等方法,Proxy会自动转发
    compiled(){return this.modes[this.mode]() || ''};
    raw(){return this.getValue().trimEnd()}
}

修改后,所有对CodeEditor实例的属性/方法访问,只要自身未定义,都会自动转发给内部的editor实例,无需逐个手动封装。

方案2:使用Object.defineProperty实现属性转发(兼容旧环境)

如果需要兼容不支持Proxy的旧浏览器,可以遍历目标对象的属性,通过Object.defineProperty动态添加到当前实例:

class B{
    constructor(z){
        this.z = z;
        // 遍历z的自身属性和原型链属性
        const proto = Object.getPrototypeOf(z);
        const allProps = [...Object.getOwnPropertyNames(z), ...Object.getOwnPropertyNames(proto)];
        
        allProps.forEach(prop => {
            if (prop in this) return;
            Object.defineProperty(this, prop, {
                get() {
                    const value = z[prop];
                    return typeof value === 'function' ? value.bind(z) : value;
                },
                set(value) {
                    z[prop] = value;
                },
                enumerable: true
            });
        });
    }
}

这种方法的局限性是只能初始化时转发已存在的属性,无法代理目标对象后续新增的属性,而Proxy可以动态拦截所有属性访问。


内容的提问来源于stack exchange,提问作者Weilory

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 16:42:05