0

I went through the following example from here

But I am not able to understand from 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;
});
Brett DeWoody
  • 59,771
  • 29
  • 135
  • 184
kittu
  • 6,662
  • 21
  • 91
  • 185
  • 3
    It’s super obsolete (since ES5, where you’d use `Object.create` instead of `new parent()` – we’re on ES2017/ES8) and poorly-written, so don’t worry too much. – Ry- Feb 19 '18 at 22:34
  • 3
    While your curiosity is understandable, that implementation looks like utter garbage to me personally, and is rendered obsolete due to the widespread usage of ES6 `class` syntax along with transpilers like Babel for backwards compatibility. Once you do understand this code, please do the internet a favor and don't try to re-use it. – Patrick Roberts Feb 19 '18 at 22:35
  • @Ryan and Patrick Roberts: I am just trying to understand it so I can understand some other examples in the JS book I am reading. – kittu Feb 19 '18 at 22:37
  • 2
    @kittu: If your JS book is talking about the implementation details of this function, it’s probably out of date. If you just need to know what it does – it’s inheritance (after defining a constructor function `Derived`, you could `Derived.inherits(Base)` to make `Derived` instances have a `Base` prototype) and a way of calling “overridden” methods on the parent class (`super.foo(x)` in ES6 and other languages would be written as `this.uber('foo', x)`). – Ry- Feb 19 '18 at 22:41
  • @Ryan I didn't understand checking of `d[name]` as in `if(t){` – kittu Feb 19 '18 at 22:48
  • Maybe if he didn't use one letter variable names for everything... I'd have to study the rest of the linked page for a while to be able to explain everything here clearly. Douglas has had some pretty bizarre opinions on JavaScript "best practices" and this is one of those cases I think highlights just that point. Another notable case is where he claims that the end of [`(function () {...})()` looks like "dog balls"](https://www.youtube.com/watch?v=eGArABpLy0k), and that was his reason for preferring `(function () {...}())` – Patrick Roberts Feb 19 '18 at 22:54
  • @kittu: It’s tracking depth to deal with the case when methods are inherited by setting `Derived.prototype.foo = Base.prototype.foo`, I think. Not going to spend too much time looking at it though, due to vertigo. – Ry- Feb 19 '18 at 23:01
  • @Ryan I'm feeling sick now. I shouldn't have tried. – Bergi Feb 20 '18 at 00:22

2 Answers2

4

I went through the following example of Douglas Crockford's uber method

Oh, you poor lost soul. Notice that this function had its origin in 2002 (or earlier), when the language standard version was still ES3. This year, we will see ES9!

You can check the web archive to see the function slowly evolving to deal with all the edge cases that were discovered, and Crockford trying to fix them. (Notice that it still fails horribly if one of the involved methods throws an exception).

Needless to say, this is totally outdated. And borked.

Can someone please explain it?

I'll try to take a shot. Lets take the following code:

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());

This does should log instance, prototype, parent, top, result - as one might have expected from a "super" call. How do these this.uber("exampleMethod") invocations - the same method called with the same arguments on the same instance - achieve this? Horrible jugglery and trickery.

We see that this.uber always calls the method that C.inherits(B) had created. B.prototype.uber is irrelevant. All calls will use the same d object (referenced by closure), which stores the recursion depth per method name. p is C.prototype, and v is B.prototype.

The first call is from the instance method (created in the constructor). d.exampleMethod is still 0 (or was just initialised to it as it didn't exist before), and we go to the else branch to select the next method to call. Here it checks p[name] == this[name], i.e. C.prototype.exampleMethod == x.exampleMethod, which is false when the instance (this/x) has an own (instance) method. So it selects the method from p, not from v, to call next. It increments the recursion count and calls it on the instance.

The second call is from the C.prototype method. If this was the first call (as usual when having only prototype methods), d.exampleMethod would be 0. Again we'd go to the else branch, but when there is no instance method we would have the comparison evaluate to true and would select v[name] to call, i.e. the parent method we inherited. It would increment the recursion count and call the selected method.

The third call would be from the B.prototype method, and d.exampleMethod would be 1. This actually already happens in the second call, because Crockford forgot to account for the instance method here. Anyway, it now goes to the if branch and goes up the prototype chain from v, assuming that the .constructor property is properly set up everywhere (inherits did it). It does so the for the stored number of times, and then selects the next method to call from the respective object - A.prototype.exampleMethod in our case.

The counting must be per-methodname, as one could attempt to call a different method from any of the invoked super methods.

At least that must have been the idea, as obviously the counting is totally off in case there is an instance method. Or when there are objects in there prototype chain that don't own the respective method - maybe also that was a case that Crockford attempted but failed to deal with.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Wow, we can even get it to make a method call itself through `uber` (remove the `x` and `C.prototype` methods from the example). At least it cannot overflow the stack due to the count. – Bergi Feb 20 '18 at 00:25
  • In case you wonder how super calls were done properly, see https://stackoverflow.com/q/16963111/1048572 – Bergi Feb 20 '18 at 00:30
  • That support for assignment to `this.exampleMethod` in a constructor with a prototype also defining `exampleMethod` was the purpose… your sacrifice is appreciated. – Ry- Feb 21 '18 at 03:36
1

If you're trying to understand how classes could be implemented using ES5 and how to implement inheritance of classes, the code example from Douglas Crockford is kinda outdated and just a pure mess. This makes it extremly difficult for beginners to understand it. (Also his explanation lacks a fair amount of detail and is not helping at all)

In my opinion a much better example of how to implement a class pattern with ES5 would be: http://arjanvandergaag.nl/blog/javascript-class-pattern.html

But netherless, lets go trough it step by step:

// 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;
});

Hope this explanation kinda helps you, but i might be wrong on some lines because the code is such a weird implementation and is far off from easily readable.

cyr_x
  • 13,987
  • 2
  • 32
  • 46