Mixins 作为类的实例?

问题描述

我正在使用实体组件系统。我已经将一些组件定义为 ES6 类,我可以通过使用 new 调用它们的构造函数来创建这些组件的实例。我正在尝试将这些类实例用作混合。我发现使用 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?

解决方法

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

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

class 语法的一个问题是 super 可能无法按预期工作,因为即使在被复制之后,这些方法仍将引用它们的原始 home 对象。 (或者这可能是您所期望的,在这种情况下您没问题。)下面有更多相关信息。

但是如果您想使用 class 语法,您可以定义一个类似 Object.assign 的函数,通过 Object.defineProperties 和 {{ 应用整个链中的所有方法和其他属性3}},这将复制 getter 和 setter。类似的东西(即兴创作,未经测试):

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,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,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,true);

const e = new Example();
e.method();
// "this is MixinBase.method"
// "this is Mixin.method"


您已经说过要使用类实例作为混入。上面的工作很好。举个例子:

function assignAll(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 复制到的新位置中工作。