How do I modify a class method? - javascript

I have code that looks like this (simplified)
class foo {
constructor() {
this.bar()
}
bar() {
// other code ...
console.log('original');
}
}
new foo(); // output: "original"
I would look to add more code inside the bar() method without affecting what already exists.
So something like this
bar() {
// original code fires
// then...
console.log('new stuff');
}
and the final output would look like
original
new stuff
A couple of notes:
I don't want to override bar() - I just want to add to it.
I don't to call bar() - it's called upstream and I just want to piggyback on that call.
I've been reading about super but that seems to be more related to sub-classes (which I don't want to create)
So I guess here's my question:
Is it possible to add to the contents of a class method in the way I described above?

This is almost certainly an XY problem, but the literal solution of your question:
class Foo {
constructor() {
this.bar()
}
bar() {
// other code ...
console.log('original');
}
}
new Foo(); // output: "original"
const addedLine = 'console.log("new stuff");\n';
const source = Foo.prototype.bar.toString();
let [_, args, body] = source.match(/^.*?\((.*?)\)\s*{([^]*)}\s*$/);
body += addedLine;
args = args.split(/\s*,\s*/);
Foo.prototype.bar = Function(...args, body);
new Foo(); // output: "original", "new stuff"
Warning: This is extremely hacky! Outside of proof of concept, you should not consider doing this in production code.
A bit less hacky solution, that mimics super (and is more restricted than the black magic above):
class Foo {
constructor() {
this.bar()
}
bar() {
// other code ...
console.log('original');
}
}
new Foo(); // output: "original"
(() => {
const oldBar = Foo.prototype.bar;
Foo.prototype.bar = function() {
oldBar.apply(this, arguments);
console.log("new stuff");
}
})();
new Foo(); // output: "original", "new stuff"
EDIT: It bugs me when classes start with a lowercase.

I recommend you add a new method inside the class and that method can use other methods in that class eg
foo.prototype.modifiedBoo = () => {
console.log("I'm modified!")
};
console.log(new foo.modifiedBoo())
// I'm modified!

If you cannot use inheritance, maybe you can use composition:
class foo {
constructor() {
this.bar()
}
bar() {
// other code ...
console.log('original');
}
}
new foo(); // output: "original"
class FooWrapper {
f;
constructor() {
this.bar();
}
bar() {
console.log("other code...");
this.f = new foo();
}
}
new FooWrapper();
class WhateverWrapper {
whatever;
constructor(wrapped) {
this.bar();
this.whatever = new wrapped();
}
bar() {
console.log("other code...");
}
}
new WhateverWrapper(foo);

Related

Modify the Constructor Function of a Class

I already have a function for modifing other class methods:
class MyClass {
constructor() {
this.num = 1;
}
inc(){
this.num++;
}
}
runAfterMethod(classHandle, methodName, executeAfter) {
const oldHandle = classHandle.prototype[methodName];
classHandle.prototype[methodName] = function () {
const returnValue = oldHandle.apply(this, arguments);
//#ts-ignore
executeAfter.apply(this, arguments);
return returnValue;
};
}
runAfterMethod(MyClass, "inc", function() {console.log(this.num)})
new MyClass().inc(); // Prints 2
However, this does not work for the constructor of the class (because it is technically the class itself)
What I would really be happy with is something that mimics this behavior:
class MyClass {
constructor() {
this.num = 1;
}
}
extendConstructor(MyClass, function(){
this.str = "hello world"
})
new MyClass(); // num: 1, str: "hello world"
I have looked at things like How to modify the constructor of an ES6 class however, they all require = somewhere, which does not work inside of a function (where that only changes the value of the class inside the function, not the class itself)
Mutating the existing class is really weird. What would be much less of a code smell IMO would be to create a wrapper around the class that instantiates the class, then calls it with your custom callback, and then returns the instance - it'd be a lot more straightforward. Would that work for your purposes?
class MyClass {
constructor() {
this.num = 1;
}
}
const extendConstructor = (theClass, callback) => function(...args) {
const instance = new theClass(...args);
callback.call(instance);
return instance;
};
const Extended = extendConstructor(MyClass, function(){
this.str = "hello world"
})
console.log(new Extended()); // num: 1, str: "hello world"

Call all methods in a class when an instance is created

Let's say I have a class that looks like this
class Foo {
bar() {
console.log('bar');
}
baz() {
console.log('baz');
}
// other methods...
}
and I want all of its methods to be unconditionally called whenever a new instance is created. How would I go about that?
I'm currently doing it by calling each method in the class constructor method
class Foo {
constructor() {
this.bar();
this.baz();
// other methods
}
bar() {
console.log('bar');
}
baz() {
console.log('baz');
}
// other methods...
}
here's a snippet of that
class Foo {
constructor() {
this.bar();
this.baz();
// other methods
}
bar() {
console.log('bar');
}
baz() {
console.log('baz');
}
// other methods...
}
new Foo();
Is there a better way to do this?
This seems to be the closest to what I want to achieve. It works by ditching class methods and using IIFEs in classProperties instead.
So something like this
class Foo {
bar = (() => {
console.log('bar');
})()
baz = (() => {
console.log('baz');
})()
}
new Foo();
and it works well on Chrome but the majority of other browsers don't yet have support for classProperties since they were added in ES9
My question is:
If I have a class with a number of methods in it, can I call all of them whenever a new instance of that class is created without having to call each individual method in the constructor method of that class?
In the context of this question, you can completely ignore the need to pass parameters to the methods. I don't need to pass any parameters to any of the class methods.
If the order in which you call the methods is not important, you can iterate dynamically over the properties of your created instance with Object.getOwnPropertyNames method:
class Foo {
constructor() {
return Object.getOwnPropertyNames (Object.getPrototypeOf (this))
.filter(propName => (propName !== 'constructor' && typeof this[propName] === 'function'))
.forEach(propName => this[propName]());
}
bar() {
console.log('bar');
}
baz() {
console.log('baz');
}
// other methods...
}
You can declare function this way:
bar = function() {
console.log('bar');
};
And iterate values of new instance, check for functions and call them:
class Foo {
constructor() {
Object.values(this).forEach(value => {
if (typeof value === 'function') {
value();
}
});
}
nonFunctionProp = 'nfp';
bar = function() {
console.log('bar');
};
baz = function() {
console.log('baz');
};
// other methods...
}
new Foo();

How to emulate Javascript `super` so `new.target` is defined?

I'm attempting to emulate this javascript without using the ES6 keywords (e.g. without class, super or extends):
class Foo {
constructor() {
if (!new.target)
console.log('Foo() must be called with new');
}
}
class Bar extends Foo {
constructor() {
super(...arguments);
}
}
var bar = new Bar();
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo); // true
I got this far, but they're not equivalent. The following throws (I log instead) while the latter does not:
function Foo() {
if (!new.target)
console.log('Foo() must be called with new');
}
function Bar() {
Foo.apply(this, arguments)
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
var bar = new Bar();
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);
So, how do I emulate providing a value for new.target when I call into Foo from Bar?
So, seems there's no apply or call that allows passing the new.target. I suppose that would defeat the purpose of new.target (though the fact that everything in JS is public really appealed to me).
So to emulate in ES5 we'd need to add something.
One solution in an answer below allocates a new object.
This solution adds new functions construct which can be chained as usual in ES5 and leave the function itself free to do nothing more than check if it's being used as a constructor.
function Foo() {
if (!new.target)
throw 'Foo() must be called with new';
console.log('Foo new check');
Foo.prototype.construct.apply(this, arguments);
}
Foo.prototype.construct = function() {
console.log('Foo construction logic');
}
function Bar() {
if (!new.target)
throw 'Bar() must be called with new';
console.log('Bar new check');
Bar.prototype.construct.apply(this, arguments);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
Bar.prototype.construct = function() {
// super()
Foo.prototype.construct.apply(this, arguments);
console.log('Bar construction logic');
}
var bar = new Bar();
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);
Bottom line, it appears ES6 features not not just syntactic sugar over ES5. Of course, they could just add a Function.prototype.super(target, arguments, newTarget) and then we could just use that. I hope they do!
Only super can call a function in Javascript and have this not be available immediately. So super is unique. And super can only be called in the context of a constructor which can only be used in the context of a class. Sooo all those key words are necessary to make super work. So Javascript has introduced a very specific object oriented feature. Looks like building a language on top of the idea of a "prototype" has it's limits.
Which is a shame...
I wonder why, all of a sudden, javascript decided to enforce this one invariant. That this is not available before the super call. Why not just make super a short hand for BaseType.prototype.constructor.call(this, ...). Why not allow it to be invoked more than once? We can blow our toes off so many other ways in Javascript, why start enforcing things now?
Well, anyway...
So, double bottom line, there exists a early bound Javascript call super that has no late bound equivalent (unlike, for example, foo.bar() which can be called late(r)bound via bar.call('foo')).
Use Object.assign to assign the parent constructor's new Foo(...arguments) to the instance:
function Foo(arg) {
if (!new.target)
throw 'Foo() must be called with new';
this.arg = arg;
this.fooProp = 'fooProp';
}
function Bar() {
Object.assign(
this,
new Foo(...arguments)
);
this.barProp = 'barProp';
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
var bar = new Bar('abc');
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);
console.log(bar);
But new Foo(...arguments) is ES6 syntax. To translate that to ES5, instead use
new (Function.prototype.bind.apply(Foo, [null, ...arguments]))()
(which takes care of the new part), which again transpiles down to
new (Function.prototype.bind.apply(Foo, [null].concat(Array.prototype.slice.call(arguments))))()
function Foo(arg) {
if (!new.target)
throw 'Foo() must be called with new';
this.arg = arg;
this.fooProp = 'fooProp';
}
function Bar() {
Object.assign(
this,
new (Function.prototype.bind.apply(Foo, [null].concat(Array.prototype.slice.call(arguments))))()
);
this.barProp = 'barProp';
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
var bar = new Bar('abc');
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);
console.log(bar);
ES6 class syntax is not syntax sugar for ES5, but it is pretty much syntax sugar for other ES6 functionality.
class Bar extends Foo {
constructor() {
super(...arguments);
}
}
is pretty similar to if you did
let Bar = function(...args) {
const _this = Reflect.construct(Object.getPrototypeOf(Bar), args, new.target);
return _this;
};
Object.setPrototypeOf(Bar, Foo);
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
where Reflect.construct constructs and object with a given new.target value and calls a given constructor with a set of arguments.
To write abstract classes, you need to make sure that the constructor of the parent class is called in the context of the prototype of the called class.
To do this, ES6 introduced "Reflect.construct".
For ES5, you can implement your own "crutch".
// Reflect.construct simulation for ES5
class Reflect2 {
static construct(TargetClass,args,ProtoClass){
let oldProto= TargetClass.prototype;
let desc=Object.getOwnPropertyDescriptor(TargetClass,'prototype');
if(desc.hasOwnProperty('value') && desc.writable===false){
if(desc.configurable===true){
desc.writable=true;
Object.defineProperty(TargetClass,'prototype',desc);
}
}
TargetClass.prototype=ProtoClass.prototype;
let self=new (Function.prototype.bind.apply(TargetClass,args));
TargetClass.prototype=oldProto;
return self;
}
}
function ParentClass(){
}
ParentClass.prototype.constructor=ParentClass;
function MyClass(){
if(new.target===undefined){throw Error();}
let self=Reflect2.construct (ParentClass,[],new.target);
// your code
return self;
}
MyClass.prototype=Object.create(ParentClass.prototype,{
constructor:{
value:MyClass
}
});
let a=new MyClass();
console.log(Object.getPrototypeOf(a)===MyClass.prototype);
"super" in ES6 is the same "this", but in the context of the parent prototype. For example, when we bind an object to a function via "bind ()", here we bind object "this" only to the prototype object.
"super" can be implemented in 2 ways:
through prototype scanning, and binding to methods and reactive properties of the 'this' object;
via Proxy object
Emulation 'super' in ES6
via Proxy object
/**
* #param {object} self - this object
* #param {function} ProtoClass - the class to observe.
Usually the current class for which methods are being written.
* #param {function} [new_target] used in the constructor.
If not passed, then the default will be the prototype constructor for "self"
(Object.getPrototyprOf(self).constructor).
* #param {boolean} [bindSelf] - if true, then the methods and reactive properties
of the parent will work in the context of self, if false, then the methods and
reactive properties will work within the framework of the object in which they
are called (i.e. within the Proxy object).
*/
function Super(self,ProtoClass,new_target,bindSelf=false){
let parentProto=Object.getPrototypeOf(ProtoClass.prototype);
let descs={};
// collect descriptors of all prototypes
do {
let buf=Object.getOwnPropertyDescriptors(parentProto);
for(let prop of Object.keys(buf)){
if(!Object.prototype.hasOwnProperty.call(descs,prop)){
descs[prop]=buf[prop];
}
}
parentProto=Object.getPrototypeOf(parentProto);
}while (parentProto!==null);
// we define the control object
let answer={};
let new_obj=function (...args){
let ParentClass=Object.getPrototypeOf(ProtoClass.prototype).constructor;
if(ParentClass===Object || ProtoClass===ParentClass){
return self;
}
new_target=new_target??Object.getPrototypeOf(self).constructor;
return Reflect.construct(ParentClass,args,new_target);
}
let parent=new Proxy(self,{
get(target,prop){
let desc=descs[prop];
if(desc===undefined){
return;
}
if(desc.hasOwnProperty('value')){
if(bindSelf && typeof desc.value==='function'){
return desc.value.bind(target);
}
return desc.value;
} else
if(desc.hasOwnProperty('get')){
return desc.get.call(target);
}
},
set(target,prop,value){
let desc=descs[prop];
if(desc===undefined || desc.hasOwnProperty('value')){
target[prop]=value;
return true;
}
if(desc.hasOwnProperty('set')){
desc.set.call(target,value);
return true;
}
}
});
Object.defineProperties(answer,{
new:{
value:new_obj
},
parent:{
value:parent
}
});
return answer;
}
How to use
function A(...args){
}
A.prototype.method=function(){
console.log('hello',this);
return this;
}
A.prototype.constructor=A;
function B(...args){
if(new.target===undefined){throw Error();}
let _super=Super(this,B,new.target);
let self= _super.new(...args);
let b=_super.parent.method(); // return proxy object
/*
in this case, all execution inside the method will occur as if this has a parent prototype.
*/
console.log(b===this);// false
return self;
}
B.prototype=Object.create(A.prototype);
B.prototype.constructor=B;
B.prototype.method=function(){
console.log('bay');
return this;
}
let b=new B();
or
function A(...args){
}
A.prototype.method=function(){
console.log('hello',this);
return this;
}
A.prototype.constructor=A;
function B(...args){
if(new.target===undefined){throw Error();}
/*
we want the methods to execute against the "this" context
and not in the "new Proxy ('this', {})" context
*/
let _super=Super(this,B,new.target,true);// arg[3] true
let self= _super.new(...args);
let b=_super.parent.method(); // return this object
console.log(b===this);// true
return self;
}
B.prototype=Object.create(A.prototype);
B.prototype.constructor=B;
B.prototype.method=function(){
console.log('bay');
return this;
}
let b=new B();
for ES5 Proxies can be replaced with a regular object, where “this” through “bind” is bound to methods.
function Super(self,ProtoClass,new_target){
let parentProto=Object.getPrototypeOf(ProtoClass.prototype);
let parent={};
// properties all prototypes
do {
let buf=Object.getOwnPropertyDescriptors(parentProto);
for(let prop of Object.keys(buf)){
if(!Object.prototype.hasOwnProperty.call(parent,prop)){
let desc=buf[prop];
if(buf[prop].hasOwnProperty('get') ){
desc.get=buf[prop].get.bind(self);
}
if(buf[prop].hasOwnProperty('set') ){
desc.set=buf[prop].set.bind(self);
}
if(buf[prop].hasOwnProperty('value') ){
if (typeof buf[prop].value === 'function') {
desc.value=buf[prop].value.bind(self);
} else{
delete desc.value;
delete desc.writable;
desc.get=function (){
return buf[prop];
};
desc.set=function(v){
self[prop]=v;
};
}
}
Object.defineProperty(parent,prop,desc);
}
}
parentProto=Object.getPrototypeOf(parentProto);
} while (parentProto!==null);
// we define the control object
let answer={};
let new_obj=function (){
let ParentClass=Object.getPrototypeOf(ProtoClass.prototype).constructor;
if(ParentClass===Object || ProtoClass===ParentClass){
return self;
}
new_target=new_target??Object.getPrototypeOf(self).constructor;
// We described the "Reflect.construct method" above. see Reflect2.construct
return Reflect.construct(ParentClass,Array.prototype.slice.call(arguments),new_target);
}
Object.defineProperties(answer,{
new:{
value:new_obj
},
parent:{
value:parent
}
});
return answer;
}
Example
function A(...args){
}
A.prototype.method=function(){
console.log('hello',this);
return this;
}
A.prototype.constructor=A;
function B(...args){
if(new.target===undefined){throw Error();}
let _super=Super(this,B,new.target);
let self= _super.new(...args);
let b=_super.parent.method();
console.log(b===this);
return self;
}
B.prototype=Object.create(A.prototype);
B.prototype.constructor=B;
B.prototype.method=function(){
console.log('bay');
return this;
}
let b=new B();
The purpose of using if (!new.target) is to check if the Foo is called with new(as a constructor), in which case we can just use if(this instanceof Foo) instead.
this could be an instance of Foo or Bar, and whether you call new Foo() or Foo.apply(this), the this instanceof Foo can always return true while it will return false if you just call Foo()(without new or apply/call).
Try this:
function Foo() {
if (!this instanceof Foo)
console.log('Foo() must be called with new');
}
function Bar() {
Foo.apply(this, arguments)
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
var bar = new Bar();
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);

Memoizee instance method in node Class

I'm seeking an elegant way to memoize a class function using Memoizee package.
Outside a class, you can go about this trivially:
const memoize = require('memoizee')
const myFunc = memoize(function myfunc(){ ... })
but inside a class block, this won't work:
class foo {
constructor(){ ... }
// Without memoization you would do:
myFunc(){ ... }
// Can't do this here:
myFunc = memoize(function myfunc(){ ... })
}
I can think of creating it in the constructor using this. syntax, but this will result in a less uniform class definition, as non-memoized methods will be declared outside the constructor:
class foo {
constructor(){
// Inside for memoized:
this.myFunc = memoize(function myfunc(){ ... })
}
// Outside for non-memoized:
otherFunc(){ ... }
}
How would you wrap an instance method?
It's possible to overwrite own method definition in the constructor
class Foo {
constructor() {
this.bar = _.memoize(this.bar);
}
bar(key) {
return `${key} = ${Math.random()}`;
}
}
const foo = new Foo();
console.log(foo.bar(1));
console.log(foo.bar(1));
console.log(foo.bar(2));
console.log(foo.bar(2));
// Output:
1 = 0.6701435727286942
1 = 0.6701435727286942
2 = 0.38438568145894747
2 = 0.38438568145894747
Depending on the way you run your code and whether or not you're using transpilation steps, maybe you can use the memoized-class-decorator with:
class foo {
constructor () { ... }
// Without memoization:
myFunc () { ... }
// With memoization:
#memoize
myFunc () { ... }
}
There is a dedicated handling for methods in memoizee. See: https://github.com/medikoo/memoizee#memoizing-methods
Still it won't work with native class syntax, best you can do at this point is something as:
const memoizeMethods = require('memoizee/methods');
class Foo {
// .. non memoized definitions
}
Object.defineProperties(Foo.prototype, memoizeMethods({
// Memoized definitions, need to be provided via descriptors.
// To make it less verbose you can use packages as 'd':
// https://www.npmjs.com/package/d
myFunc: {
configurable: true,
writable: true,
enumerable: false,
value: function () { ... }
}
});

How to wrap plain function into a constructor?

Is it possible to make a wrapper function MyFunction, which when called with new as in
instance = new MyFunction();
really returns the same object as if the callsite called SomeOtherFunction without new?
instance = SomeOtherFunction();
(I've looked at Proxy but doesn't look like they're supported in Chrome yet.)
Edit:
It turns out the callsite calls MyFunction like this:
var instance = Object.create(MyFunction.prototype);
MyFunction.apply(instance, [/* arguments */]);
// `instance` is supposed to be initialized here
I think this is what you are looking for? Note as Jan Dvorak mentioned, you can only return objects.
function SomeObject() {
return Construct();
}
function Construct() {
return { 'name' : 'yessirrreee' };
}
console.log(new SomeObject())
You can try something like this
function MyClass() {
}
var ob = new MyClass();
Edit after comments
I think this questions needs more context.
I suggest you read about higher order components, but until you've better clarified what exactly you're trying to accomplish, I cannot help you.
I really don't know what your code looks like, but here are some suggestions
either way. My guess nr2 is what you're looking for:
// 1
function MyFunction() {
this.__proto__ = SomeOtherFunction()
}
function SomeOtherFunction() {
return {
foo: function() {
return 'bar'
}
}
}
var fn = new MyFunction()
fn.foo()
// 2
function MyFunction() {}
MyFunction.prototype = SomeOtherFunction()
function SomeOtherFunction() {
return {
foo: function() {
return 'bar'
}
}
}
var fn = new MyFunction()
fn.foo()
// 3
function MyFunction() {}
MyFunction.prototype = SomeOtherFunction.prototype
function SomeOtherFunction() {
function Foo() {}
Foo.prototype.foo = function() {
return 'bar'
}
}
var fn = new MyFunction()
fn.foo()

Categories

Resources