JavaScript类中的递归

17

我正在尝试在类的上下文中使用递归方法。在我的类内部,我有以下方法:

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

然而,当我使用这种方法(之前当我只使用它作为function countChildren = ...时有效)时,递归时无法找到自身:Cannot read property 'countChildren' of undefined

有人有任何想法吗?


3
这是因为forEach的回调函数有它自己的作用域,并将this的值设置为类之外的其他内容。 - adeneo
5个回答

20
问题出在循环内部,this被重新定义为内部函数作用域。
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
@krillgar,很好的发现。 - colecmc
4
我不喜欢使用“that=this”的技巧。对于这个特定的用例,语言中有一些结构,主要是 ES6 中的 .bind() 和箭头函数。我建议使用它们来代替。 - Sumi Straessle
1
@SumiStraessle 我一点也不喜欢 this。看起来 OP 对 Javascript 还比较新,所以我不想让他们感到困惑。我看到你已经用那个回答了,所以我不会在我的回答中添加了。 - krillgar
1
没问题。我喜欢使用箭头函数来解决作用域问题,这也使代码更易读。 - Sumi Straessle
祝你在IE上好运!:-D - Sumi Straessle
显示剩余2条评论

15

尝试在构造函数中绑定该方法。
此外,通过使用箭头函数作为forEach的参数,可以保持类的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;
    }
}

这个可能可行,但我发现@krillgar的答案最为直接。谢谢回复。 - David Montgomery
2
看看我在“that = this”技巧上的评论。由于您使用类,因此您正在使用ES6语法,这意味着您可以访问箭头函数,它们不会重新绑定“this”的作用域。也试试吧,您的代码会感谢您的。 - Sumi Straessle
嗯...好的,我会再仔细看一遍。 - David Montgomery
1
如果您需要一些文档 - Sumi Straessle
我试过了,这个也可以。我同意它看起来更漂亮。 - David Montgomery

1

变量this在以下情况下被重新定义:

  1. for循环的内部作用域中
  2. 内联函数声明中
  3. 异步函数调用中

我同意krillgar关于self声明的观点。它修复了我的异步调用问题。

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

0

foreach 循环中的 this 指向当前正在迭代的元素,而不是类中的 this。

您需要绑定作用域。

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

-2

尝试使用.call()来调用函数。这样你就可以直接指定上下文。

像这样:

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

编辑: 注意到我的错误,你真正应该做的是绑定匿名函数:

像这样:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接