Capture `this` of caller inside class in JavaScript - javascript

Consider the following code:
class Abc {
funcAbc() {
console.log(this);
}
}
const abc = new Abc();
class Def {
constructor(func) {
this.func = func;
}
runFunc() {
this.func();
}
}
const def = new Def(abc.funcAbc);
def.runFunc();
I want this to be Abc when runFunc is called, but in the above implementation, this inside runFunc refers to Def. I understand this is happening because runFunc is a member of class Def. But is there any way I can capture 'original this', i.e. point this to Abc inside runFunc?
I cannot do const def = new Def(abc.funcAbc.bind(abc) because the consumer of class should not be bothered about setting the this context.
Note: This is not a theoretical question, it is an actual requirement in the project I am working on. The wrapper class takes in config, a part of which is a function. This function can also be a method on a class instance, using this inside it. Hence I need to preserve original this when calling the function from inside the wrapper class Def.

You are looking for bind() (or one of its variants).
class A {
constructor() {
this.hello = "hello";
}
run() {
console.log(this.hello);
}
}
class B {
constructor(func) {
this.func = func;
}
run() {
this.func();
}
}
const a = new A();
const b = new B(a.run.bind(a));
b.run();
When you bind() a function, it locks what this will be inside the function when the function is run, regardless of how it is invoked.
You could also wrap up the function in a closure:
class A {
constructor() {
this.hello = "hello";
this.run = ((self) => () => console.log(self.hello))(this);
}
}
class B {
constructor(func) {
this.func = func;
}
run() {
this.func();
}
}
const a = new A();
const b = new B(a.run);
b.run();
Or you could use the arrow function syntax. Note, this one requires the Babel class properties transform plugin to work:
class A {
constructor() {
this.hello = "hello";
}
run = () => {
console.log(this.hello);
}
}
class B {
constructor(func) {
this.func = func;
}
run() {
this.func();
}
}
const a = new A();
const b = new B(a.run);
b.run();

Using function.call(thisArg, args...) can set this to what ever you want. i.e., you could do:
class Abc {
func() {
console.log(this);
}
}
const abc = new Abc();
class Def {
constructor(source) {
this.func = source.func;
this.source = source;
}
runFunc() {
this.func.call(this.source);
}
}
const def = new Def(abc);
def.runFunc();
This solution assumes there will be a method on abc that is called func.

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"

How to set function to class in JS?

I have a function
function log() {
console.log('something');
}
that I want this object to have:
class Car {
constructor(name) {
this.name = name;
}
log() {
// something
}
}
How can I attach it without repeating the code inside the function?
You can attach the function as a method on the Car prototype, so now it will be available as a method on all instances.
function log() {
console.log('something');
}
class Car {
constructor(name) {
this.name = name;
}
}
Car.prototype.log = log;
const a = new Car("Audi");
const b = new Car("BMW");
a.log();
b.log();
Basically you can do it in three ways;
first way is by using class constructor and passing function in constructor call
function log() {
console.log('something');
}
class Car {
constructor(name,log) {
this.name = name;
this.log = log
}
}
const audi = new Car("name",log);
audi.log();
second way is by simply using equal operator
function log() {
console.log('something');
}
class Car {
constructor(name,log) {
this.name = name;
}
log = log
}
const audi = new Car("some");
audi.log();
Third way is by using prototype
function log() {
console.log('something');
}
class Car {
constructor(name,log) {
this.name = name;
}
}
Car.prototype.log = log;
const audi = new Car("some");
audi.log();
Like this
Either as a member function OR on the prototype
class Car {
constructor(name) {
this.name = name;
}
log = str => console.log(str); // or log = log if log is defined elsewhere
}
const carA = new Car();
carA.log("vroom");
// or
const logger = str => console.log(str);
Car.prototype.logger = logger;
const carB = new Car();
carA.logger("vroom"); // also available to the previous and other Car objects
carB.logger("vroom");

javascript bind does work for function in a dictionary

so I have a function inside a dictionary
const Dict = {
func : () => {
console.log(this);
}
}
class A {
constructor() {
this.fun = Dict.func.bind(this);
}
}
const a = new A();
a.fun();
this gives me undefined, where I'm expecting this to be a
this binding seems to work if I move the function out of the dictionary. why doesn't bind work with dictionary?
Create a function as a property instead of using an arrow function, bind doesn't work for this with arrows
const Dict = {
func() {
console.log(this);
}
}
class A {
constructor() {
this.fun = Dict.func.bind(this);
}
}
const a = new A();
a.fun();

Calling class instance as a function in JavaScript

Assuming I have a class
class foo {
constructor() {
this._pos = 0;
}
bar(arg) {
console.log(arg);
}
}
const obj = new foo();
How do I make it possible to call:
let var1 = obj('something');
You can make a callable object by extending the Function constructor, though if you want it to access the instance created, you'll actually need to create a bound function in the constructor that binds the instance to a function that is returned.
class foo extends Function {
constructor() {
super("...args", "return this.bar(...args)");
this._pos = 0;
return this.bind(this);
}
bar(arg) {
console.log(arg + this._pos);
}
}
const obj = new foo();
let var1 = obj('something ');

inherit javascript function prototype outside it closure

I am using JavaScript prototype chaining technique to chain functions as shown below:
var foo = (function () {
function fn(arg) {
if (!(this instanceof fn)) {
return new fn(arg);
}
this.arg = arg;
return this;
}
var func = function (element) {
return fn(element);
};
fn.prototype = {
bar: function () {
return this;
}
}
func.functions = fn;
return func;
}());
I would like to know how to access fn.prototype so I can add more functionality to the foo prototype outside its closure.
If I just simply do as follows, it won't work:
foo.prototype.baz = function () {
alert(this.arg);
}
foo("hello").baz();
However if fn assigned to the foo (func.functions = fn;) as it shown in the foo private closure I can do as follow and it will works:
foo.functions.prototype.baz = function () {
alert(this.arg);
}
foo("hello").baz();
Is there any other way to achieve this?
I think you are un-necessarily overcomplicating this. You can chain by simply doing this:
const foobar = function(){return this} // Initialize a new Object
const foo = text => {
const me = new foobar()
me.text = text
me.bar = a => (alert(me.text+": "+a), me)
return me
}
foo('A').bar('Test').bar('Test chained')
// Update the foobar class with baz
foobar.prototype.baz = function() {alert('BAZ worked!');return this}
foo('B').bar('1').baz().bar('2')
Note: Click Run code snippet to see the output
That's it!
Edit:
You can also do this with ES6 classes like:
class foobar {
constructor(text) {
this.text = text;
}
bar(a) {alert(this.text+": "+a);return this}
}
const foo = text => new foobar(text)
foo('A').bar('Test').bar('Test chained')
// Update the foobar class with baz
foobar.prototype.baz = function() {alert('BAZ worked!');return this}
foo('B').bar('1').baz().bar('2')

Categories

Resources