【问题标题】:Can someone please explain Douglas Crockford's uber method?有人可以解释道格拉斯·克罗克福德的超级方法吗?
【发布时间】:2018-02-19 22:32:04
【问题描述】:

我从here查看了以下示例

但我无法从 LINE 1 理解:

Function.method('inherits', function(parent){
    this.prototype = new parent();
    var d = {},
    p = this.prototype;
    this.prototype.constructor = parent;
    this.method('uber', function uber(name){   //LINE 1
        if(!(name in d)){
            d[name] = 0;
        }

        var f, r, t = d[name], v = parent.prototype;
        if(t){
            while(t){
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name];
        } else {
            f = p[name];
            if(f == this[name]){
                f = v[name];
            }
        }
        d[name] +=1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d[name] -= 1;
        return r;
    });
    return this;
});

【问题讨论】:

  • 它已经过时了(从 ES5 开始,你会使用 Object.create 而不是 new parent() - 我们在 ES2017/ES8 上)并且写得不好,所以不要太担心。
  • 虽然您的好奇心是可以理解的,但对我个人而言,该实现看起来完全是垃圾,并且由于 ES6 class 语法的广泛使用以及像 Babel 这样的转译器以实现向后兼容性,因此已经过时。一旦你理解了这段代码,请在互联网上帮个忙,不要试图重复使用它。
  • @Ryan 和 Patrick Roberts:我只是想理解它,这样我就可以理解我正在阅读的 JS 书中的一些其他示例。
  • @kittu:如果你的 JS 书在讲这个函数的实现细节,那它可能已经过时了。如果您只需要知道它的作用——它是继承(在定义构造函数Derived 之后,您可以Derived.inherits(Base) 使Derived 实例具有Base 原型)和一种调用“覆盖”方法的方式父类(super.foo(x) 在 ES6 和其他语言中会写成this.uber('foo', x))。
  • @Ryan 我不明白检查d[name] 就像if(t){ 一样

标签: javascript


【解决方案1】:

我浏览了 Douglas Crockford 的 uber 方法的以下示例

哦,你这可怜的迷失的灵魂。注意这个函数had its origin in 2002(或更早),当时语言标准版本还是ES3。今年,我们将看到 ES9!

您可以查看网络存档,了解该功能正在慢慢发展以处理所有发现的边缘情况,以及 Crockford 试图修复它们。 (请注意,如果涉及的方法之一抛出异常,它仍然会严重失败。

不用说,这完全是过时的。并且无聊

谁能解释一下?

我会尝试拍摄。让我们看看下面的代码:

function A() { }
A.prototype.exampleMethod = function() {
    console.log("top");
    return "result";
};
function B() { }
B.inherits(A);
B.prototype.exampleMethod = function() {
    console.log("parent");
    return this.uber("exampleMethod");
};
function C() {
    this.exampleMethod = function() {
        console.log("instance");
        return this.uber("exampleMethod");
    }
}
C.inherits(B);
C.prototype.exampleMethod = function() {
    console.log("prototype");
    return this.uber("exampleMethod");
};

var x = new C();
console.log(x.exampleMethod());

确实应该记录instanceprototypeparenttopresult——正如人们可能对“超级”调用所期望的那样。这些this.uber("exampleMethod") 调用(在同一实例上使用相同参数调用的相同方法)如何实现这一点? 可怕的杂耍和诡计。

我们看到this.uber 总是调用C.inherits(B) 创建的方法。 B.prototype.uber 无关紧要。所有调用都将使用相同的d 对象(由闭包引用),该对象存储每个方法名称的递归深度。 pC.prototypevB.prototype

第一次调用来自实例方法(在构造函数中创建)。 d.exampleMethod 仍然是 0(或者只是初始化为它,因为它以前不存在),我们转到 else 分支选择下一个要调用的方法。这里它检查p[name] == this[name],即C.prototype.exampleMethod == x.exampleMethod,当实例(this/x)有自己的(实例)方法时,它是假的。所以它从p而不是v中选择方法来调用next。它增加递归计数并在实例上调用它。

第二个调用来自C.prototype 方法。如果这是第一次调用(通常只有原型方法时),d.exampleMethod 将是0。我们将再次转到 else 分支,但是当没有实例方法时,我们会将比较评估为 true 并选择 v[name] 来调用,即我们继承的父方法。它会增加递归计数并调用选定的方法。

第三个调用来自B.prototype 方法,d.exampleMethod 将来自1。这实际上已经在第二次调用中发生了,因为 Crockford 忘记在这里考虑实例方法。无论如何,它现在转到if 分支并从v 向上走原型链,假设.constructor 属性在任何地方都正确设置(inherits 做到了)。它会按照存储的次数执行此操作,然后选择从相应对象调用的下一个方法 - 在我们的示例中为 A.prototype.exampleMethod

计数必须是每个方法名,因为可以尝试调用与任何调用的超级方法不同的方法。

至少必须是这样的想法,因为如果有实例方法,显然计数完全关闭。或者当原型链中存在不拥有相应方法的对象时 - 也许这也是 Crockford 尝试但未能处理的情况。

【讨论】:

  • 哇,我们甚至可以通过uber 让它自己调用一个方法(从示例中删除xC.prototype 方法)。至少它不会因为计数而溢出堆栈。
  • 如果您想知道超级调用是如何正确完成的,请参阅stackoverflow.com/q/16963111/1048572
  • 支持在构造函数中分配给 this.exampleMethod 的原型也定义了 exampleMethod 是目的……感谢您的牺牲。
【解决方案2】:

如果您想了解如何使用 ES5 实现类以及如何实现类的继承,Douglas Crockford 的代码示例有点过时了,简直是一团糟。 这使得初学者很难理解它。 (而且他的解释缺乏相当多的细节,根本没有帮助)

在我看来,如何使用 ES5 实现类模式的一个更好的例子是:http://arjanvandergaag.nl/blog/javascript-class-pattern.html

但不管怎样,让我们​​一步一步来吧:

// He extended the "prototype" of the Function object to have some syntactic sugar
// for extending prototypes with new methods (a method called 'method').

// This line extends the prototype of the Function object by a method called 'inherits' using the syntactic sugar method mentioned above.
Function.method('inherits', function(parent){
    /** 'this' is a reference to the Function the 'inherits' method is called 
     * on, for example lets asume you defined a function called 'Apple'
     * and call the method 'inherits' on it, 'this' would be a reference of 'Apple'-Function object.
    **/

    /**
     * Hes extending the prototype of the base function by the prototype of
     * the 'parent' function (the only argument the 'inherits' method takes),
     * by creating a new instance of it.
    **/
    this.prototype = new parent();
    // BAD VARIABLE NAMING!!!
    var d = {}, // variable to create a dictionary for faster lookup later on.
    p = this.prototype; // save the prototype of the base function into variable with a short name
    this.prototype.constructor = parent; // set the constructor of the base function to be the parent.


    /**
     * Extend the base function's prototype by a method called 'uber',
     * it will nearly the same function as the 'super' keyword in OO-languages,
     * but only to call methods of the parent class.
     **/
    this.method('uber', function uber(name){

        if(!(name in d)){
            // if the value name doesn't exist in the dictionary
            d[name] = 0; // set the key to the value of name and the value to 0
        }

        // BAD VARIABLE NAMING AGAIN!!!
        var f, r, t = d[name], v = parent.prototype;
        // f is used to store the method to call later on.
        // t is the value of the key inside the 'd' dictionary which indicates the depth to go up the prototype tree
        // v is the parent functions prototype
        // r is the result the method 'f' yields later on.

        // check if the attribute 'name' exists in the dicionary.
        // because if it doesn't exist t will be 0 which resolves to false.
        if(t){
            // the loop is used to walk the tree prototype tree until the implementation with the depth of t is found. 
            while(t){
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name]; // set f to the method name of the t-th parent protoype 
        } else {
            // if the attibute 'name' doesn't exist inside the dictionary
            f = p[name]; // use the method 'name' of the base class prototype.
            if(f == this[name]){
                // if the method 'name' is a member of the base class 
                f = v[name]; // use the method 'name' of the parent prototype instead.
            }
        }

        // increment the corresponding dictionary value for the depth of the 'uber' call.
        d[name] +=1;
        // call the method saved to 'f' in context of the base class and apply the 'arguments' array to it and save the result to 'r'.
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        // decrement the corresponding dictionary value for the depth of the 'uber' call.
        d[name] -= 1;
        // return the result
        return r;
    });
    return this;
});

希望这个解释对你有所帮助,但我在某些方面可能是错误的,因为代码是一个非常奇怪的实现,而且远非易读。

【讨论】:

  • v = v.constructor.prototype; 这一行发生了什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-17
  • 1970-01-01
  • 2012-07-03
  • 2011-04-06
  • 2023-03-03
  • 1970-01-01
相关资源
最近更新 更多