Thomas
Thomas

Reputation: 6196

Why can't class methods call other class methods or themselves?

I am curious why methods cannot call other methods or themselves in javascript. For example, this produces a Reference error saying add is not defined.

class sum {
  add(x, amt) {
    if(amt == 0) return x
    return add(x+1, amt-1)
  }
}
summer = new sum()
console.log(summer.add(5,5))

You must use this.add() instead.

Now I understand that the methods get translated to functions on the prototype but I don't see how that explains this limitation I'm pointing out?

Couldn't one reason that when add is defined it could have references to itself or other methods with a closure capture.

Why is it this way?

Upvotes: 4

Views: 1956

Answers (3)

briosheje
briosheje

Reputation: 7446

I will try to make it "slightly" more theorical, without going too deep in the documentations and so on.

The main answer to your question can be found by transpiling your code to plain javascript. To accomplish that, you can either use babel online or the typescript online playground. Either case, the transpiled code will look like this:

"use strict";
var sum = /** @class */ (function () {
    function sum() {
    }
    sum.prototype.add = function (x, amt) {
        if (amt == 0)
            return x;
        return add(x + 1, amt - 1);
    };
    return sum;
}());
var summer = new sum();
console.log(summer.add(5, 5));

As you can see, the add method belongs to the sum prototype, which is a function. Therefore, you can guess that accessing add whithin the add scope just can't implicitly lead to invoking the sum.prototype.add function.

Differently, if you look at the correct code:

class sum {
  add(x, amt) {
    if(amt == 0) return x
    return this.add(x+1, amt-1)
  }
}
var summer = new sum()
console.log(summer.add(5,5))

You will see that the transpiled code will invoke the this method:

"use strict";
var sum = /** @class */ (function () {
    function sum() {
    }
    sum.prototype.add = function (x, amt) {
        if (amt == 0)
            return x;
        return this.add(x + 1, amt - 1);
    };
    return sum;
}());
var summer = new sum();
console.log(summer.add(5, 5));

This is not really matter of being ambiguous, it's rather that, in javascript, such kind of invocation is allowed, because the add method is implicitly available from the global scope. Being able to access the global scope in your function scope (because remember that, whatever happens, a class in javascript is always transpiled to a function) allows to inherit the standard behavior of a function, which is having access to its parent, having its own scope and granting access to the global scope.

A little curiosity: if you actually could access this.add using add, you wouldn't be able to use undefined, since it's a global variable, hence you wouldn't be able to access it and use it, because it would implicitly be this.undefined.

So, once again, it's not about ambiguity, it's about how javascript functions works.

Upvotes: 2

deceze
deceze

Reputation: 522042

Fundamentally OOP in Javascript is very different to and other languages. There's not actually a strong concept of "classes". The classic OOP concepts like classes and instances in Javascript don't really come from "classes", but from the rules surrounding property lookups and the this and new keywords. Everything else is just functions, objects and properties—some of which are "special", like prototype.

E.g. you can assemble a "class" like this:

function Foo() {
    this.bar = 42;
}

function baz() {
    return this.bar;
}

Foo.prototype.baz = baz;

let foo = new Foo;
console.log(foo.baz());

There's no real cohesion here, baz doesn't intrinsically belong to any class, yet assembled like this they act like one. And the class syntax is just a thin veneer over this mechanism. So, if you write add inside a function, it follows variable scoping rules and will look up a variable in some surrounding scope, it's not going to find a method on a prototype.

Upvotes: 0

Adam Jenkins
Adam Jenkins

Reputation: 55623

I was in the middle of illustrating this shortcoming when @briosheje made the comment:

function add() {
   console.log('called the wrong add!');
}

class sum {
  add(x, amt) {
    if(amt == 0) return x
    return add(x+1, amt-1)
  }
}
summer = new sum()
console.log(summer.add(5,5))

Upvotes: 3

Related Questions