Pro JavaScript Techniques 笔记.part.3.1.2 类式继承 —— 我的解决

根据前篇所述的constructor问题,所需做的是找到一个方案记录构造函数的父类型,这方面可以通过闭包或者添加一个属性来标记父类型来解决。

另一个问题则是,当前构造函数创建的实例也拥有同名方法时,它在第一次调用uber()获得的是原型上的同名方法,而当第二次调用时,其调用的同名方法并不属于父类型的原型而是父类型的父类型的原型。

以下是我的解决方案,通过闭包实现了显式设置要调用的方法名来调用父级、乃至最上级的同名方法。

(function () {
    // 用于存放关联对象{child : Child, parent : Parent}的数组
    var rels = [],
    // 通过循环rels数组获得父类型的函数
        getParent = function (child) {
            var rel = null,
                i = rels.length;
            try {
                for (; i--; ) {
                    rel = rels[i];
                    if (rel.child === child) {
                        return rel.parent;
                    }
                }
            }catch(e){};
        };

    Function.method('inherits', function (Parent) {
        // 调用的父类型方法的层级
        var depth = 0, 
        // 当前构造函数原型对象
            proto = (this.prototype = new Parent()),
        // 用于判断 第一次调用的uber()是原型上的方法还是父类型上的方法
            firstUsedProto = false,
            rel = {
                parent : Parent,
                child : this
            };

        // 将关联的对象存进闭包
        rels.push(rel);
        // 指定this.prototype的constructor属性
        proto.constructor = this;

        // 下面这个方法分步介绍  @1.  @2. @3
        this.method('uber', function uber (name) {
            var func, 
                result, 
                t = depth, 
                v = Parent;

            // @2.既然上一次调用的是当前原型上的方法
            // 那么其并未调用过父类型原型上的方法  也就将depth重置为0
            // 而在指定func之后,在获得result之前,又会将depth置为1 也就确实是调用了一次父类型
            // 而将firstUsedProto置为false 则表明当前原型上的方法已使用过了
            if (firstUsedProto) {
                depth = 0;
                func = v.prototype[name];
                firstUsedProto = false;

            // @3.这部分并未做更改  通过t = depth层级 循环遍历取得父级、父级的父级...的原型上的同名方法
            } else if (t) {
                while (t) {
                    v = getParent(v);
                    t -= 1;
                }
                func = v.prototype[name];
            } else {
                // @1.第一次调用uber必定进入这个逻辑
                func = proto[name];
                // 此句判断当前实例上的这个方法是否就是原型上的方法
                if (func === this[name]) {
                // 是,则将func指定为父类型原型上的方法
                // 之后的depth += 1 也就确实代表了调用了一次父类型方法
                    func = v.prototype[name];

                // 否则,将firstUsedProto置为true
                // 那么depth改变之后,当内部再次调用到uber
                // 请看@2
                } else {
                    firstUsedProto = true;
                }
            }
            depth += 1;
            result = func.apply(this, Array.prototype.slice.apply(arguments, [1]));
            depth -= 1;
            return result;
        });
        return this;
    });
})();

测试:

var A = function (){};
A.method('get', function () {
    return 'A';
});

var B = function (){};
B.inherits(A);
B.method('get', function () {
    return 'B ' + this.uber('get');
});

var C = function (){};
C.inherits(B);
C.method('get', function () {
    return 'C ' + this.uber('get');
});

var D = function () {};
D.inherits(C);
D.method('get', function () {
    return 'D ' + this.uber('get');
});

var obj = new D ();
obj.get = function () {
    return 'the ' + this.uber('get');
};

console.log( obj.get() );

测试结果:the D C B A。至此,显式得调用指定名称的上一级方法也就实现了。性能方面可能有一些问题,闭包中包含了太多的引用
另一个问题则是,继承的类型必须在原型对象上指定相同名称的方法。如去掉下面这段,即测试中C构造函数原型上的get方法:

C.method('get', function () {
    return 'C ' + this.uber('get');
});

最后会返回the D B B A。因为get方法在C原型上并不存在,而是存在在C原型的原型链上,即[prototype]指针中指向的是B原型上的get方法。此外,这个继承,必须先声明构造函数,再调用inherit方法,最后再通过method指定prototype必须拥有的方法(自有方法),这算是一个缺陷。