Reputation: 8335
I'm not Noob in JS and I know that technically there is no right way to inherit from multiple classes. so my question is very straightforward
Is there any idea how can I create a class or just an object that acts as it inherits from two builds in JS Native object. especially EventTarget
and another object.
I try :
var map = new Map();
var eventtarget = new EventTarget();
mix = Object.create({...Map.prototype, ...EventTarget.prototype});
Object.assign(mix, et , map);
It seems not working because the methods inMap.prototype
are not itrables
also using Object.assign({}, Map.prototype, ...EventTarget.prototype)
as the same effect.
Another try:
class Base5 extends Map{
constructor(){
super();
var eventTarget = new EventTarget();
Object.assign(this,eventTarget);
}
}
Base5.prototype = Object.create(Base5.prototype)
Object.assign(Base5.prototype,EventTarget.prototype);
// that seem to work
const b5 = new Base5();
b5.set('foo','bar');
// but...
b4.addEventListener('fire', _=>_ )
// throw Uncaught TypeError: Illegal invocation at <anonymous>:1:4
This one worked but it not generic
const wm = new WeakMap();
class Base6 extends Map{
constructor(){
super();
wm.set(this, new EventTarget() )
}
addEventListener(){
wm.get(this).addEventListener(...arguments)
}
dispatchEvent(){
wm.get(this).dispatchEvent(...arguments)
}
removeEventListener(){
wm.get(this).removeEventListener(...arguments)
}
}
const b6 = new Base6();
b6.set('foo','bar'); // Map(1) {"foo" => "bar"}
b6.addEventListener('foo', e=>console.log(e) );
b6.dispatchEvent( new Event('foo') )
So anyone can come with a better approach?
Maybe Reflect.construct
can help here somehow
Upvotes: 0
Views: 227
Reputation: 8335
@trincot inspired me to research a little bit more and come up with this solution, which improves some aspects of his solution:
If you like my answer, don't forget to thumb up his answer too.
Anyway, here is a factory function that extends multiple native classes. This is a start and it probably isn't perfect, so feel free to participate in the improvement of this snippet:
function CreateMixedClasess (...classes) {
return function Mixed () {
const instances = [this, ...classes.map(cls => new cls(...arguments))]
const weakbind = (proxy, fn, obj) => new Proxy(fn, {
apply (fn, thisArg, props) {
return fn.apply(proxy === thisArg ? obj : thisArg, props)
}
})
return new Proxy(this, {
get (_, prop, proxy) {
obj = instances.find(obj => prop in obj)
const val = Object(obj)[prop]
return typeof val === 'function' ? weakbind(proxy, val, obj) : val
},
has (_, prop) { // Optional: if you care about the `in` operator
return instances.some(obj => prop in obj)
}
})
}
}
// class Mixed extends CreateMixedClasess (Map, EventTarget) {}
const Mixed = CreateMixedClasess(Map, EventTarget)
const obj12 = new Mixed()
obj12.set('foo', 'bar')
console.log('Map contains: ', Object.fromEntries(obj12))
obj12.addEventListener('foo', e => console.log('Event object type: ', e.type))
obj12.dispatchEvent(new Event('foo'))
obj12.a = 3
obj12.myfn = function () { console.log(this.a) }
obj12.myfn() // 3
obj12.myfn.call({a: 4}) // 4
class MyAwsomeClass extends CreateMixedClasess (Map, EventTarget) {
constructor () {
super(...arguments)
this.a = 33
}
logA () {
console.log(this.a)
}
}
const myAwsomeInstance = new MyAwsomeClass(obj12)
myAwsomeInstance.has('foo') // true
Upvotes: 1
Reputation: 8335
For now, and for other that seek solution to that problem I come with this soulution
const wm = new WeakMap();
function Emitter(Base) {
return class extends Base {
constructor() {
super(...arguments);
wm.set(this, new EventTarget())
}
addEventListener() {
wm.get(this).addEventListener(...arguments)
}
dispatchEvent() {
wm.get(this).dispatchEvent(...arguments)
}
removeEventListener() {
wm.get(this).removeEventListener(...arguments)
}
}
}
// how to use
const EmitterableMap = Emitter(Map);
Upvotes: 1
Reputation: 350776
You could create a function, that creates private instances of the base classes, and returns a proxy that dispatches property retrievals to one of those objects. The base classes could be passed to the constructor to keep it generic:
createMix(Map, EventTarget)
Several things will remain problematic. One blocking issue is that method calls often need this
to be set to the base object for them to work. A work around may be to return a bound method, knowing that this in itself can have undesirable effects (e.g. the customer could not take the method and bind it themselves to something else -- if that made sense at all).
For sure this does not solve all potential issues, but it seems to work in very basic usage:
function createMix(...classes) {
const obj = {};
const instances = [obj, ...classes.map(cls => new cls)];
return new Proxy(obj, {
get(obj, prop) {
obj = instances.find(obj => prop in obj);
const val = Object(obj)[prop];
return typeof val === "function" ? val.bind(obj) : val;
},
has(obj, prop) { // Optional: if you care about the `in` operator
return instances.some(obj => prop in obj);
}
});
}
// Tiny test
const obj = createMix(Map, EventTarget);
obj.set('foo','bar');
console.log("Map contains: ", Object.fromEntries(obj));
obj.addEventListener('foo', e => console.log("Event object type: ", e.type) );
obj.dispatchEvent( new Event('foo') );
As this function returns a container object, it will not be instanceof
any of the base classes passed to the function.
Upvotes: 1