13

我一直在构建一个小型 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 对象可以 go() 和 stop(),但我想要更多。我想为这个对象添加 takeOff() 和 land() 方法。之后我可以使用丑陋的点符号:

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

但这似乎是错误的,特别是如果我要添加许多方法或属性。在这里提出的问题似乎与我的非常相似,但答案对我来说并不完全正确。答案表明我应该在使用 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我试图引用它)。这是我只需要处理和处理的事情(按照代码中的祖先顺序而不是我的强迫症字母顺序)还是我在那里也忽略了一些东西?

4

2 回答 2

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

纯原型模式

您正在使用普通对象而不是构造函数 - 没有初始化。创建实例和设置继承,onlyObject.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
于 2013-01-10T21:49:36.057 回答
5

You got Object.create wrong. The first argument should be an object (maybe that's why people suggested you pass a literal).

In your first example, you're actually passing undefined:

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

The following would work, but it's not very Crockford-ish:

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

The way I believe Crockford would do it (or, at least, wouldn't complain of):

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

Note that Vehicles.Vehicle is just a literal, which will be used as the prototype for other objects. When we call Object.create, we pass Vehicles.Vehicle as the prototype, and takeOff and land will be own properties of Vehicles.Airplane. You may then call Object.create again, passing Vehicles.Airplane as the prototype, if you want to create e.g. a Boeing.

The own properties passed as the second parameter are packed in an object that contains a representation of their property descriptors. The outer keys are the names of your properties/methods, and each one points to another object containing the actual implementation as the value. You may also include other keys like enumerable; if you don't they'll take the default values. You can read more about descriptors on the MDN page about Object.defineProperty.

于 2013-01-10T21:45:08.333 回答