Objects don't inherit prototyped functions

前端 未结 1 1587
误落风尘
误落风尘 2020-11-29 06:21

I have one constructor function, which acts as a superclass:

Bla = function(a){this.a = a;}

I prototype it to include a simple method:

相关标签:
1条回答
  • 2020-11-29 06:40

    Seems like Bla2 didn't inherit it from the Bla class!

    Right. You haven't done anything to hook up inheritance there, you've just created a member of Bla2 called base which is a Bla instance. base is not a special identifier in JavaScript.

    The typical way to set up inheritance in JavaScript looks like this:

    // The base constructor function
    function Base(x) {
        // Base per-instance init
        this.x = x;
    }
    
    // An example Base function
    Base.prototype.foo = function() {
        console.log("I'm Base#foo, x = " + this.x);
    };
    
    // The Derived constructor function
    function Derived(x, y) {
        // Normally you need to call `Base` as it may have per-instance
        // initialization it needs to do. You need to do it such that
        // within the call, `this` refers to the current `this`, so you
        // use `Function#call` or `Function#apply` as appropriate.
        Base.call(this, x);
    
        // Derived per-instance init
        this.y = y;
    }
    
    // Make the Derived.prototype be a new object backed up by the
    // Base.prototype.    
    Derived.prototype = Object.create(Base.prototype);
    
    // Fix up the 'constructor' property
    Derived.prototype.constructor = Derived;
    
    // Add any Derived functions
    Derived.prototype.bar = function() {
        console.log("I'm Derived#bar, x = " + this.x + ", y = " + this.y);
    };
    

    ...where Object.create is from ES5, but it's one of the things that can easily be mostly shimmed. (Or you can use a function that only does the bare minimum without trying to do all of Object.create; see below.) And then you use it:

    var d = new Derived(4, 2);
    d.foo(); // "I'm Base#foo, x = 4"
    d.bar(); // "I'm Derived#bar, x = 4, y = 2"
    

    Live example | source

    In older code you sometimes see the Derived.prototype set up like this instead:

    Derived.prototype = new Base();
    

    ...but there's a problem with doing it that way: Base may do per-instance initialization which isn't appropriate for the entirety of Derived to inherit. It may even require arguments (as our Base does; what would we pass for x?). By instead making Derived.prototype just be a new object backed by the Base.prototype, we get the correct stuff. Then we call Base from within Derived to get per-instance init.

    The above is very basic and as you can see involves a number of steps. It also does little or nothing to make "supercalls" easy and highly-maintainable. That's why you see so many "inheritance" scripts out there, like Prototype's Class, Dean Edwards' Base2, or (cough) my own Lineage.


    If you can't rely on having ES5 features in your environment, and don't want to include a shim that does the basics of Object.create, you can just use this function in its place:

    function simpleCreate(proto) {
        function Ctor() {
        }
        ctor.prototype = proto;
        return new Ctor();
    }
    

    Then instead of

    Derived.prototype = Object.create(Base.prototype);
    

    you'd do:

    Derived.prototype = simpleCreate(Base.prototype);
    

    Of course, you can do more to automate hooking things up — which is all Lineage basically does.


    ...and finally: Above I've used anonymous functions for simplicity, e.g.:

    Base.prototype.foo = function() {
        // ...
    };
    

    ...but I don't do that in my real code, because I like to help my tools help me. So I tend to use the module pattern around each "class" (constructor function and associated prototype) and use function declarations (since I do work for the web, and IE7 and IE8 still have problems with named function expressions. So if I weren't using Lineage, I'd do the above like this:

    // Base
    (function(target) {
        // Base constructor
        target.Base = Base;
        function Base(x) {
            // Base per-instance init
            this.x = x;
        }
    
        // An example Base function
        Base.prototype.foo = Base$foo;
        function Base$foo() {
            console.log("I'm Base#foo, x = " + this.x);
        }
    })(window);
    
    // Derived
    (function(target, base) {
        // The Derived constructor function
        target.Derived = Derived;
        function Derived(x, y) {
            // Init base
            base.call(this, x);
    
            // Derived per-instance init
            this.y = y;
        }
    
        // Make the Derived.prototype be a new object backed up by the
        // Base.prototype.    
        Derived.prototype = Object.create(base.prototype);
    
        // Fix up the 'constructor' property
        Derived.prototype.constructor = Derived;
    
        // Add any Derived functions
        Derived.prototype.bar = Derived$bar;
        function Derived$bar() {
            console.log("I'm Derived#bar, x = " + this.x + ", y = " + this.y);
        }
    })(window, Base);
    

    ...or something like that. Live copy | source

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