Javascript recursion within a class

2020-02-26 07:19发布

问题:

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:

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;
}


回答2:

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;
    }
}


回答3:

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));   


回答4:

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);
}


回答5:

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));