Good Example of JavaScript's Prototype-Based Inheritance

后端 未结 11 726
情书的邮戳
情书的邮戳 2020-11-29 15:52

I have been programming with OOP languages for over 10 years but I\'m learning JavaScript now and it\'s the first time I\'ve encountered prototype-based inheritance. I tend

相关标签:
11条回答
  • 2020-11-29 16:10

    There is a snippet JavaScript Prototype-based Inheritance with ECMAScript version specific implementations. It will automatically choose which to use between ES6, ES5 and ES3 implementations according to current runtime.

    0 讨论(0)
  • 2020-11-29 16:14

    There's also Microsoft's ASP.NET Ajax library, http://www.asp.net/ajax/.

    There are a lot of good MSDN articles around as well, including Create Advanced Web Applications With Object-Oriented Techniques.

    0 讨论(0)
  • 2020-11-29 16:16

    Adding an example of Prototype based inheritance in Javascript.

    // Animal Class
    function Animal (name, energy) {
      this.name = name;
      this.energy = energy;
    }
    
    Animal.prototype.eat = function (amount) {
      console.log(this.name, "eating. Energy level: ", this.energy);
      this.energy += amount;
      console.log(this.name, "completed eating. Energy level: ", this.energy);
    }
    
    Animal.prototype.sleep = function (length) {
      console.log(this.name, "sleeping. Energy level: ", this.energy);
      this.energy -= 1;
      console.log(this.name, "completed sleeping. Energy level: ", this.energy);
    }
    
    Animal.prototype.play = function (length) {
      console.log(this.name, " playing. Energy level: ", this.energy);
      this.energy -= length;
      console.log(this.name, "completed playing. Energy level: ", this.energy);
    }
    
    // Dog Class
    function Dog (name, energy, breed) {
      Animal.call(this, name, energy);
      this.breed = breed;
    }
    
    Dog.prototype = Object.create(Animal.prototype);
    Dog.prototype.constructor = Dog;
    
    Dog.prototype.bark = function () {
      console.log(this.name, "barking. Energy level: ", this.energy);
      this.energy -= 1;
      console.log(this.name, "done barking. Energy level: ", this.energy);
    }
    
    Dog.prototype.showBreed = function () {
      console.log(this.name,"'s breed is ", this.breed);
    }
    
    // Cat Class
    function Cat (name, energy, male) {
      Animal.call(this, name, energy);
      this.male = male;
    }
    
    Cat.prototype = Object.create(Animal.prototype);
    Cat.prototype.constructor = Cat;
    
    Cat.prototype.meow = function () {
      console.log(this.name, "meowing. Energy level: ", this.energy);
      this.energy -= 1;
      console.log(this.name, "done meowing. Energy level: ", this.energy);
    }
    
    Cat.prototype.showGender = function () {
      if (this.male) {
        console.log(this.name, "is male.");
      } else {
        console.log(this.name, "is female.");
      }
    }
    
    // Instances
    const charlie = new Dog("Charlie", 10, "Labrador");
    charlie.bark();
    charlie.showBreed();
    
    const penny = new Cat("Penny", 8, false);
    penny.meow();
    penny.showGender();
    

    ES6 uses far easier implementation of inheritance witn the use of constructor and super keywords.

    0 讨论(0)
  • 2020-11-29 16:17

    ES6 class and extends

    ES6 class and extends are just syntax sugar for previously possible prototype chain manipulation, and so arguably the most canonical setup.

    First learn more about the prototype chain and . property lookup at: https://stackoverflow.com/a/23877420/895245

    Now let's deconstruct what happens:

    class C {
        constructor(i) {
            this.i = i
        }
        inc() {
            return this.i + 1
        }
    }
    
    class D extends C {
        constructor(i) {
            super(i)
        }
        inc2() {
            return this.i + 2
        }
    }
    
    // Inheritance syntax works as expected.
    (new C(1)).inc() === 2
    (new D(1)).inc() === 2
    (new D(1)).inc2() === 3
    
    // "Classes" are just function objects.
    C.constructor === Function
    C.__proto__ === Function.prototype
    D.constructor === Function
    // D is a function "indirectly" through the chain.
    D.__proto__ === C
    D.__proto__.__proto__ === Function.prototype
    
    // "extends" sets up the prototype chain so that base class
    // lookups will work as expected
    var d = new D(1)
    d.__proto__ === D.prototype
    D.prototype.__proto__ === C.prototype
    // This is what `d.inc` actually does.
    d.__proto__.__proto__.inc === C.prototype.inc
    
    // Class variables
    // No ES6 syntax sugar apparently:
    // https://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
    C.c = 1
    C.c === 1
    // Because `D.__proto__ === C`.
    D.c === 1
    // Nothing makes this work.
    d.c === undefined
    

    Simplified diagram without all predefined objects:

          __proto__
    (C)<---------------(D)         (d)
    | |                |           |
    | |                |           |
    | |prototype       |prototype  |__proto__
    | |                |           |
    | |                |           |
    | |                | +---------+
    | |                | |
    | |                | |
    | |                v v
    |__proto__        (D.prototype)
    | |                |
    | |                |
    | |                |__proto__
    | |                |
    | |                |
    | | +--------------+
    | | |
    | | |
    | v v
    | (C.prototype)--->(inc)
    |
    v
    Function.prototype
    
    0 讨论(0)
  • 2020-11-29 16:18

    As mentioned, the movies by Douglas Crockford give a good explanation about the why and it covers the how. But to put it in a couple of lines of JavaScript:

    // Declaring our Animal object
    var Animal = function () {
    
        this.name = 'unknown';
    
        this.getName = function () {
            return this.name;
        }
    
        return this;
    };
    
    // Declaring our Dog object
    var Dog = function () {
    
        // A private variable here        
        var private = 42;
    
        // overriding the name
        this.name = "Bello";
    
        // Implementing ".bark()"
        this.bark = function () {
            return 'MEOW';
        }  
    
        return this;
    };
    
    
    // Dog extends animal
    Dog.prototype = new Animal();
    
    // -- Done declaring --
    
    // Creating an instance of Dog.
    var dog = new Dog();
    
    // Proving our case
    console.log(
        "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
        "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
        dog.bark() +"\n", // Should be: "MEOW"
        dog.getName() +"\n", // Should be: "Bello"
        dog.private +"\n" // Should be: 'undefined'
    );
    

    The problem with this approach however, is that it will re-create the object every time you create one. Another approach is to declare your objects on the prototype stack, like so:

    // Defining test one, prototypal
    var testOne = function () {};
    testOne.prototype = (function () {
        var me = {}, privateVariable = 42;
        me.someMethod = function () {
            return privateVariable;
        };
    
        me.publicVariable = "foo bar";
        me.anotherMethod = function () {
            return this.publicVariable;
        };
    
        return me;
    
    }());
    
    
    // Defining test two, function
    var testTwo = ​function() {
        var me = {}, privateVariable = 42;
        me.someMethod = function () {
            return privateVariable;
        };
    
        me.publicVariable = "foo bar";
        me.anotherMethod = function () {
            return this.publicVariable;
        };
    
        return me;
    };
    
    
    // Proving that both techniques are functionally identical
    var resultTestOne = new testOne(),
        resultTestTwo = new testTwo();
    
    console.log(
        resultTestOne.someMethod(), // Should print 42
        resultTestOne.publicVariable // Should print "foo bar"
    );
    
    console.log(
        resultTestTwo.someMethod(), // Should print 42
        resultTestTwo.publicVariable // Should print "foo bar"
    );
    
    
    
    // Performance benchmark start
    var stop, start, loopCount = 1000000;
    
    // Running testOne
    start = (new Date()).getTime(); 
    for (var i = loopCount; i>0; i--) {
        new testOne();
    }
    stop = (new Date()).getTime();
    
    console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');
    
    
    
    // Running testTwo
    start = (new Date()).getTime(); 
    for (var i = loopCount; i>0; i--) {
        new testTwo();
    }
    stop = (new Date()).getTime();
    
    console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');
    

    There is a slight downside when it comes to introspection. Dumping testOne, will result in less useful information. Also the private property "privateVariable" in "testOne" is shared in all instances, als helpfully mentioned in the replies by shesek.

    0 讨论(0)
  • 2020-11-29 16:18

    I suggest looking at PrototypeJS' Class.create:
    Line 83 @ http://prototypejs.org/assets/2009/8/31/prototype.js

    0 讨论(0)
提交回复
热议问题