问题描述
我正在使用实体组件系统。我已经将一些组件定义为 ES6 类,我可以通过使用 new
调用它们的构造函数来创建这些组件的实例。我正在尝试将这些类实例用作混合。我发现使用 Object.assign
不会将这些实例的方法复制到我的目标对象上,因为这些方法绑定到对象的原型。
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 似乎不起作用。
解决方法
我可能不会使用 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
只是一个例子,上面的可运行的也是如此。这里的关键是:
-
使用
Object.getOwnPropertyDescriptors
获取属性描述符和Object.defineProperties
(或它们的单数对应物,getOwnPropertyDescriptor
和defineProperty
),以便访问器方法作为访问器传输。 -
从基础原型到实例进行工作,以便在每个级别的覆盖都能正常工作。
-
super
将继续在其原始继承链中工作,而不是在 mixin 复制到的新位置中工作。