17

I am trying to get a recursion method to work in a class context. Within my class I have the following method:

    countChildren(n, levelWidth, level) {
    if (n.children && n.children.length > 0) {
        if (levelWidth.length <= level + 1) {
            levelWidth.push(0);
        }
        levelWidth[level + 1] += n.children.length;
        n.children.forEach(function (n) {
            this.countChildren(n, levelWidth, level+1);
        });    
    }
    // Return largest openend width
    return levelWidth;
}

However, when I use this method (which worked before when I just used it as function countChildren = ...) it can't... find (?) itself: Cannot read property 'countChildren' of undefined at the recursion.

Does anyone have any ideas?

1
  • 3
    That's because the callback for forEach has it's own scope, and sets the value of this to something other than the class. Commented Jul 17, 2017 at 15:09

5 Answers 5

21

The problem arises because within your loop, this gets redefined to the inner function scope.

countChildren(n, levelWidth, level) {
    var self = this; // Get a reference to your object.

    if (n.children && n.children.length > 0) {
        if (levelWidth.length <= level + 1) {
            levelWidth.push(0);
        }
        levelWidth[level + 1] += n.children.length;

        n.children.forEach(function (n) {
            // Use "self" instead of "this" to avoid the change in scope.
            self.countChildren(n, levelWidth, level+1);
        });    
    }
    // Return largest openend width
    return levelWidth;
}
Sign up to request clarification or add additional context in comments.

7 Comments

Good catch @krillgar.
I'm not a fan of the that = this trick. There are constructs that are part of the language for this particular use case, mainly .bind() and arrow functions in ES6.
@SumiStraessle I'm not a fan of this at all. OP seems to be newer to Javascript, so I didn't want to confuse them. I see you answered with that, so I won't append to my answer.
No worries. I like arrow functions to solve the scoping hell problem, it also makes the code more readable.
@SumiStraessle I haven't spent much time with them as my last project was server side. I'll take a look at using them with TypeScript next time I'm doing client side work as my enterprise work often still unfortunately needs to target IE.
|
16

Try binding the method in the constructor.
Also, by using an arrow function for your forEach, you keep the scope of the class' this.

export class MyClass {
    constructor(){
        this.countChildren = this.countChildren.bind(this);
    }

    countChildren(n, levelWidth, level){ ... }


    countChildren(n, levelWidth, level) {
        if (n.children && n.children.length > 0) {
            if (levelWidth.length <= level + 1) {
                levelWidth.push(0);
            }
            levelWidth[level + 1] += n.children.length;
            n.children.forEach( n => { // arrow function do not need to rebind this
                this.countChildren(n, levelWidth, level+1);
            });    
        }
        // Return largest openend width
        return levelWidth;
    }
}

5 Comments

This may work, but I found @krillgar's answer to be the most straightforward. Thank you for replying.
See my comment on the that = this trick. Since you use classes, you are using ES6 syntax, which means you have access to arrow functions, which do not rebind the scope of this. Try it too. Your code will thank you.
Hmmm... okay I will have a second look then
In case you want some documentation
I tried it and this works too. I agree it looks nicer.
1

the variable this Gets redefined within:

  1. the inner scope of a for loop
  2. within a inline function declariation
  3. within asynchronous function calls.

I agree with krillgar with the declaration of self. it fixed my problem with an asynchronous call.

obj.prototype.foo = function (string){
   var self = this;
   if(string){ do something }
   else
   setTimeout(function(){
     self.foo("string");
     }, 5000);
}

Comments

0

The this inside the foreach than the this in the class. In your case, this refers to the current element being iterated.

you need to bind the scope.

n.children.forEach(function (n) {
   this.countChildren(n, levelWidth, level+1);
}.bind(this));   

Comments

-2

Try using .call() to invoke the function. That way you can specify the context directly.

Like this:

this.countChildren.call(this, n, levelWid);th, level+1

Edit: Noticed my error, what you should really do is bind the anonymous function:

like this:

  n.children.forEach(function (n) {
    this.countChildren(n, levelWidth, level+1);
  }.bind(this)); 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.