Mixins 作为 class 的一个实例?
Mixins as an instance of a class?
我正在使用实体组件系统。我已经将一些组件定义为 ES6 classes,并且我可以通过使用 new
调用它们的构造函数来创建这些组件的实例。我正在尝试将这些 class 实例用作混入。我发现使用 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 似乎不起作用。
是否有更好的方法将 class 实例用作混入?
我可能不会使用 class
语法定义 mixin。我将它们定义为对象:
const myMixin = {
doThis() {
// ...
},
doThat() {
// ...
},
// ...
};
class
语法的一个问题是 super
可能不会像您预期的那样工作,因为即使在被复制之后,这些方法仍然会引用它们的 原始 家庭对象。 (或者这可能是您所期望的,在这种情况下您没问题。)下面会详细介绍。
但是如果你想使用 class
语法,你可以定义一个类似 Object.assign
的函数,它通过 Object.defineProperties
and Object.getOwnPropertyDescriptors
应用整个链中的所有方法和其他属性,这将复制 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, 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 是一个子 class 并使用 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"
您说过要使用 class 实例 作为混入。上面的工作很好。这是一个例子:
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
只是一个例子,上面的可运行的也是如此。这里的关键是:
使用Object.getOwnPropertyDescriptors
得到属性描述符和Object.defineProperties
(或者它们的单数对应物,getOwnPropertyDescriptor
和defineProperty
),所以访问器方法作为访问器传输。
从基础原型一直工作到实例,这样每个级别的重写都能正常工作。
super
将继续在其原始继承链中工作,而不是在 mixin 已复制到的新位置。
我正在使用实体组件系统。我已经将一些组件定义为 ES6 classes,并且我可以通过使用 new
调用它们的构造函数来创建这些组件的实例。我正在尝试将这些 class 实例用作混入。我发现使用 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 似乎不起作用。
是否有更好的方法将 class 实例用作混入?
我可能不会使用 class
语法定义 mixin。我将它们定义为对象:
const myMixin = {
doThis() {
// ...
},
doThat() {
// ...
},
// ...
};
class
语法的一个问题是 super
可能不会像您预期的那样工作,因为即使在被复制之后,这些方法仍然会引用它们的 原始 家庭对象。 (或者这可能是您所期望的,在这种情况下您没问题。)下面会详细介绍。
但是如果你想使用 class
语法,你可以定义一个类似 Object.assign
的函数,它通过 Object.defineProperties
and Object.getOwnPropertyDescriptors
应用整个链中的所有方法和其他属性,这将复制 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, 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 是一个子 class 并使用 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"
您说过要使用 class 实例 作为混入。上面的工作很好。这是一个例子:
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
只是一个例子,上面的可运行的也是如此。这里的关键是:
使用
Object.getOwnPropertyDescriptors
得到属性描述符和Object.defineProperties
(或者它们的单数对应物,getOwnPropertyDescriptor
和defineProperty
),所以访问器方法作为访问器传输。从基础原型一直工作到实例,这样每个级别的重写都能正常工作。
super
将继续在其原始继承链中工作,而不是在 mixin 已复制到的新位置。