有人可以解释Douglas Crockford的超级方法吗?

时间:2018-02-19 22:32:04

标签: javascript

我从here

了解了以下示例

但我无法理解第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;
});

2 个答案:

答案 0 :(得分:4)

  

我通过以下Douglas Crockford的超级方法示例

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

您可以检查Web存档以查看缓慢演变的功能,以处理已发现的所有边缘情况,以及Crockford尝试修复它们。 (请注意,如果其中一个涉及的方法抛出异常,它仍然会失败。)

毋庸置疑,这完全是过时的 borked

  

有人可以解释一下吗?

我会尝试一下。让我们采取以下代码:

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 - 正如人们可能预期的那样a"超级"呼叫。这些this.uber("exampleMethod")调用 - 在同一个实例上使用相同的参数调用的相同方法 - 如何实现这一点? 可怕的 jugglery和trickery。

我们看到this.uber始终调用C.inherits(B)创建的方法。 B.prototype.uber无关紧要。所有调用都将使用相同的d对象(由closure引用),该对象存储每个方法名称的递归深度。 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

计数必须是per-methodname,因为可以尝试从任何调用的super方法中调用不同的方法。

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

答案 1 :(得分:1)

如果您正在尝试了解如何使用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;
});

希望这个解释有点帮助你,但我可能在某些方面有误,因为代码是如此奇怪的实现,并且远非易读。