理解 Javascript 超级方法模拟代码

Understanding Javascript super method emulation code

我正在阅读 this article 关于 Javascript 中的超级方法。最下面是作者使用的一种方法,本质上就是给每一个方法函数对象加上一个名字属性,然后用它在当前调用super的对象的原型链上寻找匹配的方法。

我将复制下面的 code

var Base = function() {};

// Use the regular Backbone extend, but tag methods with their name
Base.extend = function() {
  var Subclass = Backbone.Model.extend.apply(this, arguments);
  _.each(Subclass.prototype, function(value, name) {
    if (_.isFunction(value)) {
      value._methodName = name;
    }
  });
  return Subclass;
};

// Define a special `super` property that locates the super implementation of the caller
Object.defineProperty(Base.prototype, "super", {
  get: function get() {
    var impl = get.caller,
      name = impl._methodName,
      foundImpl = this[name] === impl,
      proto = this;

    while (proto = Object.getPrototypeOf(proto)) {
      if (!proto[name]) {
        break;
      } else if (proto[name] === impl) {
        foundImpl = true;
      } else if (foundImpl) {
        return proto[name];
      }
    }

    if (!foundImpl) throw "`super` may not be called outside a method implementation";
  }
});

它使用了Underscore.js和Backbone.js,我不是很熟悉,但它们的用法不是疑点所在。

设置super属性getter时,声明了4个变量:implnamefoundImplproto.

impl持有调用super的方法,通过get.caller获得。 name 是调用 super 的方法的名称(或者 undefined 如果 super 不是从方法内部调用的)。 proto 持有调用该方法的对象,其原型链将被横向直到我们找到超级方法。

现在 foundImpl 让我有点困惑。它是一个布尔值,最初被赋值this[name] === impl。由于 this 指向调用方法的对象,因此 this[name] 将 return 方法本身,即 ===impl。每次都是这样,除非 nameundefined(我们在方法外调用super)。

while(proto = Object.getPrototypeOf(proto)) 然后开始遍历调用对象的原型链,从直接父级开始,直到到达 null.

if(!proto[name]) 检查当前原型中是否存在同名方法。如果不存在,它会跳出循环,如果 foundImpl 为假,则会抛出错误。如前所述,我能看到这种情况发生的唯一情况是如果 super 在方法外部调用,其中 name 将是 undefined,因此 this[name] === impl 将是错误的出色地。否则,因为 foundImpl 从一开始就已经是真的。

else if (proto[name] === impl) 将检查当前具有相同名称的原型方法是否严格等于调用 super 的方法。老实说,我想不出这是真的情况,因为要从一个方法调用 super ,它必须被覆盖,从而使两个不同的函数对象。例如:

var a = { method: function(){ return "Hello!"; } };
var b = Object.create(a);
console.log(a.method === b.method); //true
b.method = function(){ return "Hello World!"; };
console.log(a.method === b.method); //false

也许这毕竟只是一次安全检查,永远不会达到这个条件?

最后,else if (foundImpl) 将检查 foundImpl 是否为真(很可能在第一个循环迭代中,上述特殊情况除外)和 return 当前proto[name] 方法如果是。


所以我怀疑第二个条件的意义是什么:else if (proto[name] === impl)?它涵盖什么情况? foundImpl到底有什么作用?

哇,我好久没有想过那个博客 post 了!我相信大多数常青浏览器此时已经放弃了对 arguments.caller 的支持,这使得演示代码有点难以编写,但我会尽力解释

您认为令人困惑的行是 foundImpl = this[name] === impl,乍一看,它的计算结果总是 true。需要它的原因(实际上首先让我对 "the super problem" 感兴趣的原因)是您链接 super 调用的情况。

考虑这样的设置:

const Base = /* ... */;

const First = Base.extend({
  sayHello() {
    console.log('First#sayHello()');
  }
});

const Middle = First.extend({
  sayHello() {
    this.super();
    console.log('Middle#sayHello()');
  }
});

const Last = Middle.extend({
  sayHello() {
    this.super();
    console.log('Last#sayHello()');
  }
});

如果我调用 new Last().sayHello()super getter 将被调用两次。第一次,正如你所说 this[name] === impl 马上就会成立。

second 调用中,调用者将是来自 Middle 的函数,但 this[name] 将是来自 [=21= 的函数],因此 this[name] === impl 将为假。

然后继续执行原型遍历循环,往上走一步,我们会发现proto[name] === impl,所以foundImpl会被设置为true,我们就再进行一次循环并正确 return 来自 Base.

的函数