I an encountering a problem which I can't solve. I don't know whether it is a lack knowledge, or the fact that it is not even possible in Javascript, but I hope to get to know it.
I am trying to execute a list of function aliases in an object. When executing these functions, I would like to use them as if they were executed right from the instance itself, So I can use other methods and instance variables within that called method. To make my explaination a bit more clear, here is an example:
class Bar {
constructor() {
this.name = "Bar";
}
someMethod() {
console.log(this.name) // should log Bar
}
}
class Foo {
constructor() {
this.name = "Foo";
}
someOtherMethod() {
console.log(this.name) // should log Foo
}
}
const bar = new Bar();
const foo = new Foo();
const methodList = {
foo: bar.someMethod,
baz: foo.someOtherMethod,
}
for(let prop in methodList) {
methodList[prop](); // logs 2x undefined
}
for(let prop in methodList) {
methodList[prop].apply(foo); //logs 2x Foo
}
As can be seen in the example above, this.name is a variable in the instance of the class. When executing the second loop, a context is applied and logs correctly, as expected. I would like to see that context being applied automatically, since the function alias object is executed in a different file, not knowing about foo or bar and just receiving the list.
Is there any way to achieve this?
You could wrap your foo and bar methods in a function of their own. Within these methods you could then call the object's method someMethod()/someOtherMethod() on the object like so:
const methodList = {
foo: (...args) => bar.someMethod(...args),
baz: (...args) => foo.someOtherMethod(...args),
}
At the moment your first loop doesn't work because your this doesn't refer to the actual context of the object as that isn't what is used to invoke the method. It instead refers to your methodList
See example bellow:
class Bar {
constructor() {
this.name = "Bar";
}
someMethod() {
console.log(this.name) // should log Bar
}
}
class Foo {
constructor() {
this.name = "Foo";
}
someOtherMethod() {
console.log(this.name) // should log Foo
}
}
const bar = new Bar();
const foo = new Foo();
const methodList = {
foo: (...args) => bar.someMethod(...args),
baz: (...args) => foo.someOtherMethod(...args),
}
for(let prop in methodList) {
methodList[prop](); // correct logs
}
This happens because the contextual this when invoking methodList[prop]
is methodList, hence this in someMethod and someOtherMethod is actually:
{
foo: bar.someMethod,
baz: foo.someOtherMethod,
}
To solve the issue, you can wrap the methods in an anonymous function returning the invoked method, as shown below:
class Bar {
constructor() {
this.name = "Bar";
}
someMethod(a,b,c) {
console.log(a,b,c,this.name) // should log Bar
}
}
class Foo {
constructor() {
this.name = "Foo";
}
someOtherMethod(a,b,c) {
console.log(a,b,c,this.name) // should log Foo
}
}
const bar = new Bar();
const foo = new Foo();
const methodList = {
foo: (...args) => bar.someMethod(...args), // <-- anonymous function that, once invoked, returns `bar.someMethod()`, hence the contextual `this` of someMethod will be `bar`.
baz: function() { // <-- same as above, just written without the lambda notation.
return foo.someOtherMethod(...arguments);
}//^
}// | <-- that evaluation is actually calling the above code block.
// |-------------------------------------|
for(let prop in methodList) {// |
methodList[prop](1,4,'hello'); // <------|
}
Related
I have a class declaration which is quite large with lots of methods.
It's something like this:
class Foo {
constructor() {
// ...code
}
config() {
// ...code
}
theme() {
// ...code
}
register() {
// ...code
}
render() {
// ...code
}
write() {
// ...code
}
}
I'd like to split the source code up so it's easy to manage and read.
Is there a way I can assign a method to a class dynamically within the class itself?
I'd like to do something like:
function bar(value) {
if (value) {
this.baz = 'baz'
return this
}
}
class Foo {
constructor() {
// ...code
}
new bar() // Would reference function above ^ (not valid)
}
I know this is not valid or correct but because the class syntax automatically creates and binds a property called bar() to the prototype of Foo I can't think how you would do it, and perhaps the functionality doesn't exist.
The closest I can think to splitting the code up, is to extend a class but this seems a bit verbose.
class Bar {
bar() {
if (value) {
this.baz = 'baz'
return this
}
}
}
class Foo extends Bar {
constructor() {
super()
}
}
bar() is now in Foo's prototype.
This method also offers little control to creating aliases if needed and also is harder to make it clear what methods that are available in the Foo class.
Is there a better way?
I can think of two ways of adding external functions as own properties:
Set it as a property of the this inside the constructor of the class.
Set it as a public property of the class, this is the proposal.
The downside is that, all the instances will not share the same function object as it becomes an own property. Own properties are used for holding state not functions.
To add in prototype, which is where methods in ES6 class are added, so that all instances share the same function object. You have to add it to the prototype property of the class. This works as class is a syntactic sugar of the function constructor and every function has a prototype property which is inherited by all instances of that class.
class Foo {
constructor() {
// ...code
this.bar = bar; //becomes own property
}
//As a public field, becomes own property
baz = baz;
}
function bar(){
console.log("from bar");
}
function baz(){
console.log("from baz");
}
function foobar(){
console.log("from foobar");
}
const foo = new Foo();
foo.bar();
foo.baz();
//Set it directly on the prototype prop of the Class
Foo.prototype.foobar = foobar;
foo.foobar();
Also, I presume there isn't a way to set a prototype of a class inside
the class definition itself?
From your question in the comment, yes you can set the function in the prototype of the class inside the class definition itself:
function bar(){
console.log("bar in prototype");
console.log("accessing data from this: ", this.data);
}
class Foo{
constructor(){
this.data = "data"
Foo.prototype.bar = bar;
Foo.prototype.thisBar = bar.bind(this);
}
//methods are added in prototypes
baz(){
console.log("baz in prototype");
}
}
const foo = new Foo();
foo.bar();
foo.baz();
//accessing from prototype, here this is global context and will print undefined
Object.getPrototypeOf(foo).bar();
//this is bound so always refers to the instance
Object.getPrototypeOf(foo).thisBar();
//same for methods in es6
Object.getPrototypeOf(foo).baz();
For static methods just set it either as a property of the class or set it as public static property.
class Bar{
constructor(){
//some logic
}
static baz = baz;
}
function foo(){
console.log("from foo");
}
function baz(){
console.log("from baz");
}
Bar.foo = foo;
Bar.foo();
Bar.baz();
What different between assigning property on Object and Object.prototype?
for example
Object.test =function(){};
and
Object.prototype.test =function(){}
The first gives Object a static method that can be invoked directly from the class, without an instance. For example:
Object.test =function(){
console.log('Object test running');
};
Object.test();
Assigning a function to the prototype, on the other hand, allows for instances to run the method:
Object.prototype.test = function() {
console.log('test running on object ', this);
};
// don't use the object constructor, this is just an example:
const obj = new Object();
obj.test();
It might make a bit more sense if you didn't use the built-in Object, which everything inherits from:
function Foo() {}
Foo.checkIfFoo = function(arg) {
return arg instanceof Foo;
};
const f = new Foo();
console.log(Foo.checkIfFoo(f));
Here, foo.checkIfFoo is a helper function on Foo that checks if a passed object is an instance of Foo or not - no instance is required to run checkIfFoo. Functions on the prototype, on the other hand, require an instance to run:
function Foo() {
this.info = 'A Foo instance';
}
Foo.prototype.checkInfo = function() {
console.log(this.info);
};
const f = new Foo();
f.checkInfo();
Note that in ES6+, you can put a function directly on the class with the static keyword:
// roughly equivalent to the snippet with checkIfFoo above
class Foo {
static checkIfFoo(arg) {
return arg instanceof Foo;
}
}
const f = new Foo();
console.log(Foo.checkIfFoo(f));
Whereas a standard method lacks the static keyword:
// roughly equivalent to the snippet with checkInfo above
class Foo {
constructor() {
this.info = 'A Foo instance';
}
checkInfo() {
console.log(this.info);
}
}
const f = new Foo();
f.checkInfo();
I have two files that declare a class, both of them with static functions:
foo.js:
const bar = require('./bar');
console.log('bar in foo: ');
console.log(bar);
class foo {
static _(test) {
return 'foo_' + test + bar._(test);
}
static bar() {
return 'bar';
}
}
module.exports = foo;
bar.js:
const foo = require('./foo');
console.log('foo in bar:');
console.log(foo);
class bar {
static _(test) {
return foo.bar(test);
}
}
module.exports = bar;
In VS Code, code completion work in both files to get the other file's static method.
But when I try to execute it:
const foo = require('./foo');
foo._('test');
I can see that the recursive require don't work as I thought it would:
foo in bar:
{}
bar in foo:
[Function: bar]
bar.js:8
return foo.bar(test);
^
TypeError: foo.bar is not a function
I know class in JS are syntaxic sugar around functions, but I am still surprised that the interpreter lose static members.
Can anybody explain the working of require in that case? And why it result in a empty object?
I can do this in JavaScript:
var output = String(result);
And I can do this with the same object that is referenced using String:
var character = String.fromCharCode(10);
String can be used as a function to construct an object and members can be called on it without using it as a constructor. How do I make an object usable in both these ways? What is this called?
You are talking about class methods.
function Foo() {
this.bar = 3
}
Foo.baz = function() {
console.log('hi');
}
or in ES 2015
class Foo {
static baz () {
console.log('hi');
}
}
Response to the comments
You can define a static method in the constructor function because the constructor function is necessarily in scope:
function Foo() {
Foo.method = function () {
// do stuff
}
}
There are a couple of problems with this approach though:
Foo.method('stringy string'); // TypeError: cannot read property 'method' of undefined
Because the static method is defined in the constructor, it won't be there until the constructor function runs at least once:
const foo = new Foo();
Foo.method('stringy string'); // now we're good
which leads to another problem, now we're wastefully reassigning that method every time the constructor runs. You can avoid that with a conditional check:
function Foo() {
if (!Foo.method) Foo.method = function....
}
But that's a lot of weird stuff just to avoid defining the class method after the constructor, and it still doesn't solve the first problem.
You can make a class with static methods:
class Foo {
constructor(bar) {
this.bar = bar;
}
length() {
return this.bar.length;
}
static fromThing(thing) {
return new Foo(thing.bar);
}
}
Foo.fromThing() is analogous to String.fromCharCode()
Simply
function MyClass () {
this.val = 1;
}
MyClass.staticMethod = function () {/* code here */};
I'm learning javascript and want to define 2 classes inside an object. The second class is derived from the first class.
It looks like:
let foo = {
bar: (function () {
let bar_msg = '';
class bar {
constructor(msg) {
bar_msg = msg;
}
}
return bar;
}()),
baz: (function () {
let baz_msg = '';
class baz extends foo.bar {
constructor(msg) {
super();
baz_msg = msg;
}
}
return baz;
}())
};
Error message:
Uncaught ReferenceError: foo is not defined
Then, I've tried to split the classes to multiple parts:
let foo = {};
foo.bar = (function () {
let bar_msg = '';
class bar {
constructor(msg) {
bar_msg = msg;
}
}
return bar;
}());
foo.baz = (function () {
let baz_msg = '';
class baz extends foo.bar {
constructor(msg) {
super();
baz_msg = msg;
}
msg() {
return baz_msg;
}
}
return baz;
}());
let b = new foo.baz('hi!');
console.log(b.msg());
It works.
So, my question is: Why do I get the difference? I cannot show some google search results because I have no idea about the keywords.
An object is 'completely' defined when you declared it and assign it a value like so:
var foo = 'bar';
You may declare a variable but not assign it to any value:
var foo;
and when you try to access it, it will give you undefined. You may also assign a value to an undeclared variable:
foo = 'bar';
And the javascript will automatically create a global variable foo. If you try to access a variable that has never been declared or implicitly declared (e.g foo = 'bar'). It will throw an error;
console.log(iDoNotExist); // will throw an error!
In your first code, the class baz, could not locate foo in its own scope, so it will go to the global scope. But since it's also not available in the global scope and it has neither been declared explicitly nor implicitly, it will throw an error.
In the second code, you have explicitly declared foo and assign it to an object and added a property bar right after it. Any code written after foo is declared would be able to access it, including when you assign the property bar to foo, in which a class called baz tries to extend foo.bar. If the piece of code that tries to extend foo or foo.bar is written after the definition of foo. It will throw an error. See snippet below for example:
class baz extends foo {
constructor(msg) {
super();
baz_msg = msg;
}
msg() {
return baz_msg;
}
}
let foo = {};
let b = new foo.baz('hi!');
In summary, you get the difference because the accessibility of foo. In the first variable you try to access it before it is both neither explicitly nor implicitly defined, resulting in an error. In the second variable, you've declared and assigned it to a value and try to access it after, which is perfectly legal.