Reputation: 597
Is there a way to call parent method automatically if the property name are the same.
// library part
var parent = {
foo: function() { console.log('foo from parent'); },
bar: function() { console.log('bar from parent'); }
}
// User part
var child = Object.create(parent);
child.foo = function() { console.log('foo from child'); };
child.bar = function() { console.log('bar from child'); };
The output if i call child.foo()
is foo from child
, and my objective output is both foo from child
and foo from parent
is console logged out. For now when i need to do that, i use this code:
child.foo = function() {
console.log('foo from child');
this.__proto__.foo();
}
I have a reason to do that, it's like library that provide a parent object that can be extended by the user and i don't want to remind the user to call this.__proto__.foo()
every time they assign the method which the property name are the same with the property from parent object.
The solution must be modifying the library part of course (parent object or maybe in object creation), not the user part.
Upvotes: 2
Views: 120
Reputation: 2119
I guess another way to do this, without calling the proto explicitly would be to use a factory function:
const createChild = parent => Object.create(parent);
const enchanceChild = parent => ({
foo() {
console.log('from child');
parent.foo();
}
});
const parent = {
foo(){ console.log('from parent') },
}
const child = createChild(parent);
const enchancedChild = enchanceChild(child);
enchancedChild.foo(); // from child form parent
It is just another approach of doing what you already did above.
What do you think?
EDIT:
Using Proxy Objects a more cleaner approach:
var createObject = parent => {
var handler = {
get(target, name) {
if (typeof target[name] === 'function' && typeof parent[name] === 'function') {
return function() {
const res = target[name]();
parent[name]();
return res;
}
}
return target[name];
}
}
return new Proxy({}, handler);
}
const parent = { foo() { console.log('parent'); }, };
const child = createObject(parent);
child.foo = () => console.log('child');
child.foo();
// returns:
// child
// parent
Upvotes: 0
Reputation: 1074138
The solution must be modifying the library part of course (parent object or maybe in object creation), not the user part.
If that means you really want the users to be able to write
child.foo = function() { ... };
...then that changes things a bit, but it's still possible without resorting to proxies (which are really slow). First, my original answer, since you'd accepted it, then an update accounting for that:
i don't want to remind the user to call
this.__proto__.foo()
every time they assign the method which the property name are the same with the property from parent object
In that case, you probably want to give them a worker function they use to set those function properties, and automatically detect the situation and handle it. You'll have to decide whether to call the prototype's version before or after the user's new code, if you're not going to let them control where the call goes, and decide whether to return the result of their function or the prototype's.
Naturally, nothing prevents them from choosing to ignore that, but there's only so much you can do.
For instance, this example assumes you want to call the original after (as shown in your example) and return the result of their override.
var mainProto = {
addMethod: function(name, method) {
var p = Object.getPrototypeOf(this); // See note later
if (typeof p[name] === "function") {
this[name] = function() {
var result = method.apply(this, arguments);
p[name].apply(this, arguments);
return result;
};
} else {
this[name] = method;
}
return this;
}
};
var parent = Object.create(mainProto);
parent.addMethod("foo", function foo() { console.log('foo from parent'); });
parent.addMethod("bar", function bar() { console.log('bar from parent'); });
var child = Object.create(parent);
child.addMethod("foo", function foo() { console.log('foo from child'); });
child.addMethod("bar", function bar() { console.log('bar from child'); });
var grandChild = Object.create(child);
grandChild.addMethod("foo", function foo() { console.log('foo from grandChild'); });
grandChild.addMethod("bar", function bar() { console.log('bar from grandChild'); });
console.log("---");
parent.foo();
parent.bar();
console.log("---");
child.foo();
child.bar();
console.log("---");
grandChild.foo();
grandChild.bar();
.as-console-wrapper {
max-height: 100% !important;
}
About __proto__
vs getPrototypeOf
: __proto__
is officially only supported on browsers, not in other JavaScript environments, and is defined on Object.prototype
so it's entirely possible to have an object without it (via Object.create(null)
). In constrast, getPrototypeOf
is defined across environments and works regardless of whether the object inherits from Object.prototype
.
We can use a setter on the properties in question to insert our wrappers instead of having the user call addMethod
:
// Library code
function wrapMethod(newMethod, parentMethod) {
if (typeof parentMethod === "function") {
return function() {
var result = newMethod.apply(this, arguments);
parentMethod.apply(this, arguments);
return result;
};
}
return newMethod;
}
function defineMethod(obj, name, method) {
Object.defineProperty(obj, name, {
set: function(newValue) {
if (this === obj) {
return;
}
defineMethod(this, name,wrapMethod(newValue, Object.getPrototypeOf(this)[name]));
},
get: function() {
if (this === obj) {
return method;
}
return this[name];
}
});
}
var parent = (function() {
var _foo = function foo() { console.log("foo from parent"); };
var _bar = function bar() { console.log("bar from parent"); };
var parent = {};
defineMethod(parent, "foo", _foo);
defineMethod(parent, "bar", _bar);
return parent;
})();
// User code
var child = Object.create(parent);
child.foo = function foo() { console.log('foo from child'); };
child.bar = function bar() { console.log('bar from child'); };
var grandChild = Object.create(child);
grandChild.foo = function foo() { console.log('foo from grandChild'); };
grandChild.bar = function bar() { console.log('bar from grandChild'); };
console.log("---");
parent.foo();
parent.bar();
console.log("---");
child.foo();
child.bar();
console.log("---");
grandChild.foo();
grandChild.bar();
.as-console-wrapper {
max-height: 100% !important;
}
Upvotes: 3