【问题标题】:Understanding Javascript super method emulation code理解Javascript超级方法仿真代码
【发布时间】:2018-07-14 19:33:58
【问题描述】:

我正在阅读 this article 关于 Javascript 中的超级方法。在最底部,作者使用了一种方法,它本质上是为每个方法函数对象添加一个 name 属性,并使用它在当前调用 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] 将返回方法本身,即===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 是否为真(它很可能在第一次循环迭代中,除了上述特殊情况)并返回当前的 proto[name] 方法(如果是)。


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

【问题讨论】:

    标签: javascript inheritance super


    【解决方案1】:

    哇,我好久没有想到那篇博文了!我相信大多数常青浏览器在这一点上已经放弃了对arguments.caller 的支持,这使得演示代码有点难以编写,但我会尽力解释?

    您所说的令人困惑的行是foundImpl = this[name] === impl,乍一看,它总是会评估为true。需要它的原因(实际上是我首先对“super 问题”感兴趣的原因)是您链接了 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 将立即成立。

    虽然在第二调用中,调用者将是来自Middle的函数,但this[name]将是来自Last的函数,所以this[name] === impl将是假的。

    然后执行会继续进入原型遍历循环,再上一步,我们会发现proto[name] === impl,所以foundImpl会被设置为true,我们再循环一遍时间并从Base正确返回函数。

    【讨论】:

    • 非常感谢丹,完美的解释!现在一切都说得通了:)
    猜你喜欢
    • 1970-01-01
    • 2018-04-03
    • 2020-01-03
    • 2020-10-23
    • 2013-05-21
    • 2016-10-22
    • 1970-01-01
    • 2016-06-09
    相关资源
    最近更新 更多