克罗克福德的原型继承 - 用法

时间:2013-01-10 21:13:36

标签: javascript inheritance

我一直在构建一个小型JS框架供我工作使用,我想使用Douglas Crockford的原型继承模式。我想我对原型对象的工作方式有了一般的了解,但不清楚的是我在这个模式中使用这种模式的方式超出了最简单的例子。

我会把它充实到我明白的地步。

(function () {

    'use strict';

    var Vehicles = {};

    Vehicles.Vehicle = function () {
        this.go = function () {
            //go forwards
        };

        this.stop = function () {
            //stop
        };
    };

    Vehicles.Airplane = Object.create(Vehicles.Vehicle());

}());

所以现在我的Vehicles.Airplane对象可以去()和停止(),但我想要更多。我想将takeOff()和land()方法添加到此对象。我之后可以使用丑陋的点符号:

Vehicles.Airplane.takeOff = function () {
    //take off stuff
}

但这似乎是错误的,特别是如果我要添加许多方法或属性。 question asked at here似乎与我的非常相似,但答案对我来说并不完全正确。答案表明我应该在使用Object.create之前构建一个对象文字,并且我应该将该对象文字传递给create方法。但是,在给出的示例代码中,看起来它们的新对象现在根本没有继承。

我希望的是一些类似于:

的语法
Vehicles.Airplane = Object.create(Vehicles.Vehicle({
    this.takeOff = function () {
        //takeOff stuff
    };
    this.land = function () {
        //land stuff
    };
}));

我知道这个语法现在会用Object.create打破,因为我当然正在传递Vehicle.Vehicle一个函数而不是一个对象文字。这不是重点。我想知道我应该以什么方式将一个新属性构建到一个继承自另一个对象的对象中,而不必在事后用点符号一次列出一个。


编辑:

Bergi,经过对这个主题的一些痛苦的想法,我想我真的想要用你所描述的“古典模式”。这是我对它的第一次尝试(现在使用实际的代码片段,而不是模拟假设 - 你甚至可以看到我糟糕的方法存根):

CS.Button = function (o) {
    o = o || {};

    function init(self) {
        self.domNode = dce('a');
        self.text = o.text || '';
        self.displayType = 'inline-block';
        self.disabled = o.disabled || false;

        self.domNode.appendChild(ctn(self.text));
        if (o.handler) {
            self.addListener('click', function () {
                o.handler(self);
            });
        }
    }

    this.setText = function (newText) {
        if (this.domNode.firstChild) {
            this.domNode.removeChild(this.domNode.firstChild);
        }
        this.domNode.appendChild(ctn(newText));
    };

    init(this);
};
CS.Button.prototype = Object.create(CS.Displayable.prototype, {
    constructor: {value: CS.Button, configurable: true}
});

CS.Displayable = function (o) { // o = CS Object
    o = o || {};

    var f = Object.create(new CS.Element(o));

    function init(self) {
        if (!self.domAnchor) {
            self.domAnchor = self.domNode;
        }
        if (self.renderTo) {
            self.renderTo.appendChild(self.domAnchor);
        }
    }

    //Public Methods
    this.addClass = function (newClass) {
        if (typeof newClass === 'string') {
            this.domNode.className += ' ' + newClass;
        }
    };
    this.addListener = function (event, func, capture) {
        if (this.domNode.addEventListener) {
            this.domNode.addEventListener(event, func, capture);
        } else if (this.domNode.attachEvent) {
            this.domNode.attachEvent('on' + event, func);
        }
    };
    this.blur = function () {
        this.domNode.blur();
    };

    this.disable = function () {
        this.disabled = true;
    };

    this.enable = function () {
        this.disabled = false;
    };

    this.focus = function () {
        this.domNode.focus();
    };

    this.getHeight = function () {
        return this.domNode.offsetHeight;
    };

    this.getWidth = function () {
        return this.domNode.offsetWidth;
    };

    this.hide = function () {
        this.domNode.style.display = 'none';
    };

    this.isDisabled = function () {
        return this.disabled;
    };

    this.removeClass = function (classToRemove) {
        var classArray = this.domNode.className.split(' ');
        classArray.splice(classArray.indexOf(classToRemove), 1);
        this.domNode.className = classArray.join(' ');
    };

    this.removeListener = function () {
        //Remove DOM element listener
    };

    this.show = function () {
        this.domNode.style.display = this.displayType;
    };

    init(this);
};
CS.Displayable.prototype = Object.create(CS.Element.prototype, {
    constructor: {value: CS.Displayable, configurable: true}
});

我应该很清楚,并说它还没有完成工作,但大多数情况下,我希望你对我是否在正确的轨道上有所了解。您在示例中的注释中提到了“特定于实例的属性和方法”。这是否意味着我的this.setText方法和其他方法被错误地放置,并且原型链上的后代项目不可用?

此外,使用时,似乎声明的顺序现在很重要(我无法访问CS.Displayable.prototype,因为(我认为)首先列出CS.Button,当时CS.Displayable未定义我试图引用它)。这是我必须要做的事情(在代码中按照祖先的顺序而不是我的OCD字母顺序排列)或者是否有我在那里俯瞰的东西?

2 个答案:

答案 0 :(得分:13)

Vehicles.Airplane = Object.create(Vehicles.Vehicle());

那条线是错的。您似乎想要使用new Vehicles.Vehicle - 永远不要在没有new的情况下调用构造函数!

但是,我不确定你想要使用哪种模式。我想到了两个:

经典模式

您正在使用构造函数,就像在标准JS中一样。通过继承彼此的原型对象并在子实例上应用父构造函数来完成继承。您的代码应如下所示:

Vehicles.Vehicle = function () {
    // instance-specific properties and methods,
    // initialising
}
Vehicles.Vehicle.prototype.go = function () {
     //go forwards
};
Vehicles.Vehicle.prototype.stop = function () {
    //stop
};

Vehicles.Airplane = function() {
    // Vehicles.Vehicle.apply(this, arguments);
    // not needed here as "Vehicle" is empty

    // maybe airplane-spefic instance initialisation
}
Vehicles.Airplane.prototype = Object.create(Vehicles.Vehicle.prototype, {
    constructor: {value:Vehicles.Airplane, configurable:true}
}); // inheriting from Vehicle prototype, and overwriting constructor property

Vehicles.Airplane.prototype.takeOff = function () {
   //take off stuff
};

// usage:
var airplane = new Vehicles.Airplace(params);

Pure Prototypical Pattern

您正在使用普通对象而不是构造函数 - 没有初始化。要创建实例和设置继承,仅使用Object.create。它就像只有原型对象和空构造函数。 instancof在这里不起作用。代码如下所示:

Vehicles.Vehicle = {
    go: function () {
         //go forwards
    },
    stop: function () {
         //stop
    }
}; // just an object literal

Vehicles.Airplane = Object.create(Vehicles.Vehicle); // a new object inheriting the go & stop methods
Vehicles.Airplane.takeOff = function () {
   //take off stuff
};

// usage:
var airplane = Object.create(Vehicles.Airplane);
airplane.prop = params; // maybe also an "init" function, but that seems weird to me

答案 1 :(得分:5)

你错了Object.create第一个参数应该是一个对象(也许这就是人们建议你传递文字的原因)。

在您的第一个示例中,您实际上正在传递undefined

Vehicles.Airplane = Object.create(Vehicles.Vehicle()); // the function call will
                                                       // return undefined

以下是可行的,但它不是非常Crockford-ish:

Vehicles.Airplane = Object.create(new Vehicles.Vehicle());

我相信Crockford会这样做(或者,至少,不会抱怨):

var Vehicles = {};

Vehicles.Vehicle = {
    go : function() {
        // go stuff
    },
    stop : function() {
        // go stuff
    }
};

Vehicles.Airplane = Object.create(Vehicles.Vehicle, {
    takeOff : { 
        value : function() {
            // take-off stuff
        }
    },
    land : {
        value: function() {
            // land stuff
        }
    }
});

请注意Vehicles.Vehicle只是一个文字,它将用作其他对象的原型。当我们致电Object.create时,我们会将Vehicles.Vehicle作为原型,takeOffland将成为Vehicles.Airplane的自有属性。然后,您可以再次致电Object.create,将Vehicles.Airplane作为原型传递,如果您想创建例如value波音公司。

作为第二个参数传递的属性被打包在一个包含property descriptors表示的对象中。外键是属性/方法的名称,每个键都指向包含实际实现的另一个对象enumerable。您还可以包含其他键,例如{{1}};如果你不这样做,他们将采用默认值。您可以在MDN page about Object.defineProperty上阅读有关描述符的更多信息。