开发者

An example of prototypical inheritance that does not involve simulating classical inheritance?

I've read the following QAs and all of them examine using prototypical inheritance to simulate classical inheritance.

Good Example of JavaScript's Prototype-Based Inheritance

javascript inheritance

Using inheritance patterns in JavaScript

Is there not one worki开发者_开发问答ng example of prototypical inheritance in the wild? Simulating life-forms, maybe? What problems, other than those created or not adequately solved by the programming language, would benefit from raw prototypical inheritance?


Inheritance is inheritance, so you can get the same basic functionality out of either.

One benefit of prototypal inheritance in JavaScript is to allow dynamic run-time addition of new methods or alteration of old ones available to all objects (without adding a per-object memory penalty).

This can be dangerous, especially when the built-in methods such as String or Object, have their built-in methods over-ridden in a backward-breaking (or forward-threatening) manner.

String.prototype.replace = function () {
    return 'hahaha';
};

But it can be powerful when some browsers' or libraries' implementations are inadequate in functionality or lagging in performance.

It also helps modularity, extensibility, and improvement of libraries. If you include someone's library and find that their implementation for a particular method could be better optimized, you can drop in their code without tampering with it, while still having the ability to improve it or add features to it and benefiting all objects defined outside their library (at least as soon as you start adding it to the prototype). A library could even swap implementations based on user preference (probably not usually a good idea though if it may interfere with other code using that method) or let them dynamically define the names of the methods they want to use.

And the prototypal behavior even comes into play even within a "class" since you can take advantage of the convenience of storing directly on the object (though it does add to memory in such cases and probably better just to create a new class--but it still can be convenient).

function Dog (type) {
    if (type === 'poodle') {
        this.bark = function () {
            alert('(yapyapyap)');
        };
    }
}
Dog.prototype.bark = function () {
    alert('(woof)');
};

var muffy = new Dog('poodle');
muffy.bark(); // '(yapyapyap)'
var rover = new Dog();
rover.bark(); // '(woof)'

The fact that the prototype is something which can be dynamically changed or swapped in the prototypal approach of JavaScript also lets you dynamically create new classes at run-time unlike some more traditional languages, at the very least offering some more succinct expressivity:

function Creature () {}
Creature.prototype.respire = function () { return 'oooooh'; };

function createClass (o, f) {
    f = f || function f () {}
    f.prototype = (typeof o === 'function') ? o.prototype : o.constructor.prototype;
    f.prototype.constructor = f;
    return f;
}

var animals = ['Dog', 'Tiger', 'Lion', 'Frog', 'Kangaroo'];
animals.forEach(function (animal) {
    window[animal] = createClass(Creature);
});
var rover = new Dog();

Finally, you can avoid strict is-a hierarchies, by borrowing just what you need, while still taking advantage of the inheritable features:

function createMixinClass (old, constructor, newMethods) {
    if (typeof constructor === 'object') {
        newMethods = constructor;
        constructor = null;
    }
    var proto = old.prototype, constructor = constructor || function () {};

    for (var m in proto) {
        constructor.prototype[m] = proto[m];
    }
    for (var method in newMethods) {
        if (!newMethods[method]) {
            delete constructor.prototype[method];
        }
        else {
            constructor.prototype[method] = newMethods[method];
        }
    }
    return constructor;
}

var Cat = createMixinClass(Dog, {bark:null, meow: function () {alert('meow');}});
var kitty = new Cat();

In short, I don't think there's anything so different that lets you handle new kinds of problems, but it offers more flexibility, especially with some reusable utilities made handy.


It's not a "teaching" example, but a "real world" use of prototypal inheritance is jQuery plugins. $.fn is actually the prototype of the magical jQuery collection, and jQuery plugins add methods to it in order to add functionality to any jQuery collection.


There are some interesting links on the Self Language Blog including some videos.


There are a whole lot of examples of prototypal inheritance in the wild. Notably, jQuery uses it. Every time you make a jQuery selection, you're using a delegate prototype to inherit jQuery methods. It's also in common use in a variety of web applications, including Abode's Creative Cloud platform, many products by Yahoo, etc...

In fact, every implementation of classical inheritance in JavaScript is actually employing prototypal inheritance to mimic classical inheritance -- necessary only as a convenience for programmers who are more familiar with classical than prototypal inheritance. Prototypal inheritance is so flexible that it's trivial to mimic the features of classical inheritance using prototypal inheritance. The reverse is not true.

Prototypal inheritance means simply that an object inherits directly from another object. Here's an example:

var switchProto = {
  isOn: function isOn() {
    return this.state;
  },

  toggle: function toggle() {
    this.state = !this.state;
    return this;
  },

  state: false
},
switch1 = Object.create(switchProto),
switch2 = Object.create(switchProto);

It's common to put that Object.create() call in a factory function to make object instantiation more convenient.

There are lots of problems with classical inheritance that don't exist with prototypal inheritance, such as:

Classical Inheritance

Tight coupling. Inheritance is the tightest coupling available in OO design. Descendant classes have an intimate knowledge of their ancestor classes.

Inflexible hierarchies (aka duplication by necessity). Single parent hierarchies are rarely capable of describing all possible use cases. Eventually, all hierarchies are "wrong" for new uses -- a problem that necessitates code duplication.

Multiple inheritance is complicated. It's often desirable to inherit from more than one parent. That process is inordinately complex and its implementation is inconsistent with the process for single inheritance, which makes it harder to read and understand.

Brittle architecture. Because of tight coupling, it's often difficult to refactor a class with the "wrong" design, because much existing functionality depends on the existing design.

The Gorilla / Banana problem. Often there are parts of the parent that you don't want to inherit. Subclassing allows you to override properties from the parent, but it doesn't allow you to select which properties you want to inherit.

Prototypal Inheritance

To understand how prototypal inheritance solves these problems, you should first understand that there are two different types of prototypal inheritance. JavaScript supports both:

Delegation. If a property is not found on an instance, it is searched for on the instance's prototype. This enables you to share methods among many instances, giving you the flyweight pattern for free.

Concatenation. The ability to dynamically add properties to an object enables you to freely copy any properties from one object to another, all together, or selectively.

You can combine both forms of prototypal inheritance to achieve a very flexible system of code reuse. So flexible in fact, that it's trivial to implement classical inheritance with prototypes. The reverse is not true.

Prototypal inheritance allows most of the important features you'll find in classical languages. In JavaScript, closures and factory functions allow you to implement private state, and functional inheritance can be easily combined with prototypes in order to add mixins that support data privacy, as well.

Some advantages of prototypal inheritance:

Loose coupling. An instance never has a need to make a direct reference to a parent class or prototype. It's possible to store a reference to an object's prototype, but it's ill advised, because that would promote tight coupling in the object hierarchy -- one of the biggest pitfalls of classical inheritance.

Flat hierarchies. It's trivial with prototypal OO to keep inheritance hierarchies flat - using concatenation and delegation, you can have a single level of object delegation and a single instance, with no references to parent classes.

Trivial multiple inheritance. Inheriting from multiple ancestors is as easy as combining properties from multiple prototypes using concatenation to form a new object, or a new delegate for a new object.

Flexible architecture. Since you can inherit selectively with prototypal OO, you don't have to worry about the "wrong design" problem. A new class can inherit any combination of properties from any combination of source objects. Due to the ease of hierarchy flattening, a change in one place doesn't necessarily cause ripples throughout a long chain of descendant objects.

No more gorillas. Selective inheritance eliminates the gorilla banana problem.

I am not aware of any advantage that classical inheritance has over prototypal inheritance. If anybody is aware of any, please enlighten me.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜