2006-12-31

A bug of Douglas Crockford's uber

In Douglas Crockford's famous Classical Inheritance in JavaScript, he write a function named 'uber' which simulate 'super' for OO programming. Someone pointed out a bug of it, but don't understand why and just try to write his own OO solution.

Below is the test case:


function BaseClass() {}
BaseClass.prototype.getName = function() {
    return "BaseClass(" + this.getId() + ")";
}
BaseClass.prototype.getId = function() {
    return 1;
}

function SubClass() {}
SubClass.inherits(BaseClass);
SubClass.prototype.getName = function() {
    return "SubClass(" + this.getId() + ") extends " +
        this.uber("getName");
}
SubClass.prototype.getId = function() {
    return 2;
}

function MyClass() {}
MyClass.inherits(SubClass);
MyClass.prototype.getName = function() {
    return "MyClass(" + this.getId() + ") extends " +
        this.uber("getName");
}
MyClass.prototype.getId = function() {
    // Should always return 2 which is the result of SubClass.getId()
    return this.uber("getId");
}

alert(new TopClass().getName());

//Expect result: "MyClass(2) extends SubClass(2) extends BaseClass(2)"
//Actual result:"MyClass(2) extends SubClass(1) extends BaseClass(1)"

I did some research about this interesting bug, and got the patch.

Douglas Crockford's original source


Function.prototype.inherits = function (parent) {
    var d = 0, p = (this.prototype = new parent());
    this.prototype.uber = function (name) {
        var f, r, t = d, 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 += 1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d -= 1;
        return r;
    }
    return this;
}

My patched version


Function.prototype.inherits = function(parent) {
    var d = {}, p = (this.prototype = new parent());
    
    this.prototype.uber = function(name) {
     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;
    }
}

The problem is because of d, which indicates the depth of supercall. In original code, every method share the same d, which will cause bug when nest call (one method call another). So give every method their own counter, solves the problem.

No comments: