【问题标题】:Mixins as an instance of a class?Mixins 作为类的实例?
【发布时间】:2021-07-06 02:28:47
【问题描述】:

我正在使用实体组件系统。我已经将一些组件定义为 ES6 类,我可以通过使用 new 调用它们的构造函数来创建这些组件的实例。我正在尝试将这些类实例用作 mixins。我发现使用Object.assign 不会将这些实例的方法复制到我的目标对象上,因为这些方法绑定到对象的原型。

我找到了一个 hacky 解决方案,如下所示:

function getAllPropertyNames(obj) {
  return Object
    .getOwnPropertyNames(obj)
    .concat(Object.getOwnPropertyNames(obj.__proto__))
    .filter(name => (name !== "constructor"))
}

function assembleFromComponents(obj, ...cmp) {
  for(let i of cmp) {
    for(let j of getAllPropertyNames(i)) {
      obj[j] = i[j]
    }
  }
}

这种方法并不理想,因为它不访问组件的完整原型链,尽管我认为无论如何我都不需要它。但是,经过检查,getter 和 setter 似乎不起作用。

有没有更好的方法将类实例用作 mixin?

【问题讨论】:

    标签: javascript class multiple-inheritance mixins entity-component-system


    【解决方案1】:

    我可能不会使用class 语法定义混合。我将它们定义为对象:

    const myMixin = {
        doThis() {
            // ...
        },
        doThat() {
            // ...
        },
        // ...
    };
    

    class 语法的一个问题是super 可能无法按您预期的那样工作,因为即使在被复制之后,这些方法仍然会引用它们的原始 home 对象。 (或者这可能是你所期望的,在这种情况下你很好。)下面有更多内容。

    但是如果你想使用class 语法,你可以定义一个类似Object.assign 的函数,通过Object.definePropertiesObject.getOwnPropertyDescriptors 应用整个链中的所有方法和其他属性,这将复制getter和二传手。类似的东西(即开即用,未经测试):

    function assignAll(target, source, inherited = false) {
        // Start from the prototype and work upward, so that overrides work
    
        let chain;
        if (inherited) {
            // Find the first prototype after `Object.prototype`
            chain = [];
            let p = source;
            do {
                chain.unshift(p);
                p = Object.getPrototypeOf(p);
            } while (p && p !== Object.prototype);
        } else {
            chain = [source];
        }
        for (const obj of chain) {
            // Get the descriptors from this object
            const descriptors = Object.getOwnPropertyDescriptors(obj);
            // We don't want to copy the constructor or __proto__ properties
            delete descriptors.constructor;
            delete descriptors.__proto__;
            // Apply them to the target
            Object.defineProperties(target, descriptors);
        }
        return target;
    }
    

    使用它:

    assignAll(Example.prototype, Mixin.prototype);
    

    现场示例:

    function assignAll(target, source, inherited = false) {
        // Start from the prototype and work upward, so that overrides work
    
        let chain;
        if (inherited) {
            // Find the first prototype after `Object.prototype`
            chain = [];
            let p = source;
            do {
                chain.unshift(p);
                p = Object.getPrototypeOf(p);
            } while (p && p !== Object.prototype);
        } else {
            chain = [source];
        }
        for (const obj of chain) {
            // Get the descriptors from this object
            const descriptors = Object.getOwnPropertyDescriptors(obj);
            // We don't want to copy the constructor or __proto__ properties
            delete descriptors.constructor;
            delete descriptors.__proto__;
            // Apply them to the target
            Object.defineProperties(target, descriptors);
        }
        return target;
    }
    
    class Example {
        method() {
            console.log("this is method");
        }
    }
    const mixinFoos = new WeakMap();
    class Mixin {
        mixinMethod() {
            console.log("mixin method");
        }
        get foo() {
            let value = mixinFoos.get(this);
            if (value !== undefined) {
                value = String(value).toUpperCase();
            }
            return value;
        }
        set foo(value) {
            return mixinFoos.set(this, value);
        }
    }
    
    assignAll(Example.prototype, Mixin.prototype, true);
    
    const e = new Example();
    e.foo = "hi";
    console.log(e.foo);
    // HI

    这是一个示例,其中 mixin 是一个子类并使用super,只是为了演示super 在该上下文中的含义:

    function assignAll(target, source, inherited = false) {
        // Start from the prototype and work upward, so that overrides work
    
        let chain;
        if (inherited) {
            // Find the first prototype after `Object.prototype`
            chain = [];
            let p = source;
            do {
                chain.unshift(p);
                p = Object.getPrototypeOf(p);
            } while (p && p !== Object.prototype);
        } else {
            chain = [source];
        }
        for (const obj of chain) {
            // Get the descriptors from this object
            const descriptors = Object.getOwnPropertyDescriptors(obj);
            // We don't want to copy the constructor or __proto__ properties
            delete descriptors.constructor;
            delete descriptors.__proto__;
            // Apply them to the target
            Object.defineProperties(target, descriptors);
        }
        return target;
    }
    
    class Example {
        method() {
            console.log("this is Example.method");
        }
    }
    
    class MixinBase {
        method() {
            console.log("this is MixinBase.method");
        }
    }
    
    class Mixin extends MixinBase {
        method() {
            super.method();
            console.log("this is Mixin.method");
        }
    }
    
    assignAll(Example.prototype, Mixin.prototype, true);
    
    const e = new Example();
    e.method();
    // "this is MixinBase.method"
    // "this is Mixin.method"

    你说过你想使用类 instances 作为 mixins。上面的工作很好。这是一个例子:

    function assignAll(target, source, inherited = false) {
        // Start from the prototype and work upward, so that overrides work
    
        let chain;
        if (inherited) {
            // Find the first prototype after `Object.prototype`
            chain = [];
            let p = source;
            do {
                chain.unshift(p);
                p = Object.getPrototypeOf(p);
            } while (p && p !== Object.prototype);
        } else {
            chain = [source];
        }
        for (const obj of chain) {
            // Get the descriptors from this object
            const descriptors = Object.getOwnPropertyDescriptors(obj);
            // We don't want to copy the constructor or __proto__ properties
            delete descriptors.constructor;
            delete descriptors.__proto__;
            // Apply them to the target
            Object.defineProperties(target, descriptors);
        }
        return target;
    }
    
    class Example {
        method() {
            console.log("this is Example.method");
        }
    }
    
    class MixinBase {
        method() {
            console.log("this is MixinBase.method");
        }
    }
    
    const mixinFoos = new WeakMap();
    class Mixin extends MixinBase {
        constructor(value) {
            super();
            this.value = value;
        }
        mixinMethod() {
            console.log(`mixin method, value = ${this.value}`);
        }
        get foo() {
            let value = mixinFoos.get(this);
            if (value !== undefined) {
                value = String(value).toUpperCase();
            }
            return value;
        }
        set foo(value) {
            return mixinFoos.set(this, value);
        }
        method() {
            super.method();
            console.log("this is Mixin.method");
        }
    }
    
    // Here I'm using it on `Example.prototype`, but it could be on an
    // `Example` instance as well
    assignAll(Example.prototype, new Mixin(42), true);
    
    const e = new Example();
    e.mixinMethod();
    // "mixin method, value = 42"
    e.method();
    // "this is MixinBase.method"
    // "this is Mixin.method"
    e.foo = "hi";
    console.log(e.foo);
    // "HI"

    但实际上,您可以随心所欲地设计它; assignAll 只是一个例子,上面的可运行的也是如此。这里的关键是:

    1. 使用Object.getOwnPropertyDescriptors 获取属性描述符和Object.defineProperties(或它们的单数对应物,getOwnPropertyDescriptordefineProperty),以便访问器方法作为访问器传输。

    2. 从基础原型一直到实例,这样每个级别的覆盖都可以正常工作。

    3. super 将继续在其原始继承链中工作,而不是在 mixin 已复制到的新位置。

    【讨论】:

    • 是的,我几乎总是看到 mixins 被定义为对象。我想做的是允许这些 mixin 的数字等略有变化。类语法似乎是为此目的保持可读性的最简单方法。编辑:我刚刚测试了它,不幸的是它不起作用
    • @EnderShadow8 - (我已经用一个可运行的示例更新了答案。)使用class 语法的一个问题是super,它不会像预期的那样运行在混合中使用。 (好吧,好吧,这取决于你的期望。:-D)
    • @EnderShadow8 - 也刚刚更新以显示使用super 的示例,它揭示了我原来的assignAll 中的一个愚蠢的错误,我已经修复了。 :-)
    • 我的问题不是关于使用类作为混合,而是关于使用类 instance 作为混合,它允许我将参数传递给构造函数.我的问题是方法绑定到原型而不是实例。恐怕我们在谈论完全不同的问题。我会尽量澄清我的问题。
    • @EnderShadow8 - 如果您将它与实例而不是原型一起使用,上述方法就可以正常工作。我将添加一个示例。
    猜你喜欢
    • 2011-02-21
    • 1970-01-01
    • 1970-01-01
    • 2019-04-04
    • 1970-01-01
    • 1970-01-01
    • 2012-09-17
    • 2019-08-16
    • 1970-01-01
    相关资源
    最近更新 更多