关于超类:在javascript超级调用中突变’this’,如何处理?

mutated 'this' in javascript super call, how to deal with?

很抱歉,如果很难解释的话。假设类Y扩展类Z,类X扩展类Y

问题是,如果一个类没有一个方法,它称之为超级类,目前为止还不错。

X.prototype.v = function{return this.getV();} //getV() doesn't exist in class X

由于类X扩展了类Y,并且类Y中存在getv(),因此调用如下:

1
Y.prototype.getV = function{return this.parent().getV()+1}

工作函数parent()返回其超级类的一个实例。假设z还有一个getv方法,它返回一个真正的int值。

1
Z.prototype.getV = function{return 1}

所以类y中的getv()函数意味着返回z加1的getv值,并将其发送到最低的类x。

大部分有线部分在这里。只要从x.v()调用getv方法,y.getv()中的"this"就指向x,而不是y!

所以y中的getv()函数变成x.parent().getv()+1,我得到"超过了最大调用堆栈大小"。

解决这个问题的一个愚蠢但非常有效的方法是写

1
this.parent().parent().getV()+1

双亲使发送者Z不是Y,然后在调用x.getv()时返回2

这很愚蠢,因为如果调用者是y本身,就像y.getv(),我认为"this"在这里正确地表示y,那么有太多的parent()调用,导致它未定义。

有些想法,比如我们可以摆脱"这个",用另一种方法获得当前类。不太理想的方法可能是跟踪所有类的所有函数,并设置正确数量的parent()。我相信还有更多。然而,他们都没有经过测试。

从上面的代码片段中选取的最小代码示例可以是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Z{

}
class Y extends Z{

}
class X extends Y{

}

X.prototype.v = function{
    return this.getV();
}

Y.prototype.getV = function{
    return this.parent().getV()+1;
}

Z.prototype.getV = function{
    return 1;
}

var x = new X();
console.log(x.v());


如果使用ES6类语法,则应使用super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Z {
  getV() {
    return 1;
  }
}
class Y extends Z {
  getV() {
    return super.getV() + 1;
  }
}
class X extends Y {
  v() {
    return super.getV(); // or this.getV();
  }
}
new Z().getV(); // 1
new Y().getV(); // 2
new X().getV(); // 2
new X().v();    // 2

在ES5中,我会使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function extend(f1, f2) {
  f1.prototype = Object.create(f2.prototype);
  f1.prototype.constructor = f1;
  f1.super = f2.prototype;
}
function Z(){}
Z.prototype.getV = function() {
  return 1;
};
function Y(){}
extend(Y, Z);
Y.prototype.getV = function() {
  return Y.super.getV.call(this) + 1;
};
function X(){}
extend(X, Y);
X.prototype.v = Y.prototype.getV;
new Z().getV(); // 1
new Y().getV(); // 2
new X().getV(); // 2
new X().v();    // 2


this是指调用函数的上下文,或者用更实际的术语来说:它是指调用中出现在点之前的对象。调用abc.def()时,def中的thisabc相同。

所以最简单的可能是改变这一点:

1
X.prototype.v = function{return this.getV();}

到:

1
X.prototype.v = function{return this.parent().getV();}

这使得y的this为上面的this.parent()。这样,您就不必更改原始呼叫。

但是,如果您希望保持定义x.prototype.getv的灵活性,那么最好立即定义它,而不是进行上述更改:

1
2
X.prototype.v = function{return this.getV();}
X.prototype.getV = function{return this.parent().getV();}

备选方案:使用Apply/Call

您可以使用apply方法(或call方法,这是相同的另一种味道),明确地引用目标函数应该用作this

1
X.prototype.v = function{return this.getV.apply(this.parent(), arguments);}

测试原型是否定义了自己的方法:

您可以使用hasOwnProperty

1
2
3
4
X.prototype.v = function{
    var nextThis = X.prototype.hasOwnProperty('getV') ? this : this.parent();
    return this.getV.apply(nextThis, arguments);
}

`