Can't get the inherited class functions in JS

2019-06-12 16:53发布

问题:

I build some base PopupBuilder class and want to implement it in some different usecases. For each case I create a class extends the base class.
So:

function PopupBuilder () {
    var _this = this;

    this.buildPopup = function () {
        var popup = $('<div>', {
        id: "popup",
        class: "full-page",
        click: clickHandler});
    } //works fine

    function acceptChanges () {}
    function clickHandler (event) { // works fine 
        console.log(_this); // _this doesn't overrides by child class _this. Should it? 
        acceptChanges(event); // doesn't overrides by child class function as well
    }
}

function ToServerSender (text) {
    var _this = this;
    function acceptChanges (event) {
        // send data to server
    }
}

ToServerSender.prototype = new PopupBuilder();

var updateFile = new ToServerSender();
updateFile.buildPopup();

I create an instance of child class, why I can't get to child class code and override/extend the base class? How should I rewrite the code it will works?

回答1:

You need to bear in mind that the way OOP works in JavaScript is vastly different from class-style OOP.

So let's break it down a bit:

var _this = this;

This assignment takes place when the new PopupBuilder() constructor is called toward the end of the code you posted there. The this here refers to an object of type PopupBuilder, and through inheritance is essentially "shared" among all instances of ToServerSender. Each ToServerSender does not get its own copy of this _this.


function acceptChanges () {}

This is just a function that exists inside your PopupBuilder constructor. It's not a method. It can't be overridden by defining a new one in the ToServerSender constructor:


console.log(_this); // _this doesn't overrides by child class _this. Should it? 

As explained above, the _this here is referring to the one instance of _this that was created the one time you called the PopupBuilder constructor and it is shared among all instances of ToServerSender.


acceptChanges(event); // doesn't overrides by child class function as well

This is simply a closure to the acceptChanges function inside the PopupBuilder constructor. It can't be overridden.


So what can you do?

1. Use overridable methods.

One thing you need to do is assign methods to this so that child objects can override them:

function PopupBuilder () {
    var _this = this;

    this.buildPopup = function () {} //works fine

    this.acceptChanges = function () {}

    this.clickHandler = function (event) { 
        console.log(this); 
        this.acceptChanges(event); 
    }
}

Here, we have the constructor assigning functions to properties of this so that they can be inherited via the prototype chain and overridden as needed:

function ToServerSender (text) {
    var _this = this;

    // overrides prototype's  .acceptchanges
    this.acceptChanges = function (event) {
        // send data to server
    }
}

2. Create the child type's prototype using Object.create(), not a constructor.

Using a constructor to create a prototype on a child type is a bit outdated. The new way to do this is to use Object.create, to create a copy of the parent object's prototype:

ToServerSender.prototype = Object.create(PopupBuilder.prototype);

But this leaves the ToServerSender prototype without all the stuff that was initialized in the PopupBuilder constructor. This is actually a good thing, because the PopupBuilder constructor was creating one copy of its private variables that were going to be shared between all of the ToServerSenders in existence, potentially wreaking havok.

The solution to this is to...

3. Call the parent constructor from the child constructor:

function ToServerSender(text) {
    PopupBuilder.call(this);

    var _this = this;
    this.a = "hello";
    //this class overrides acceptChanges from PopupBuilder
    this.acceptChanges = function(event) {
        // send data to server
        console.log(1);
        console.log(_this);
    }
}

What happens here is that ToServerSender calls the PopupBuilder constructor on itself, so that when _this is created inside that constructor, it is referring to the ToServerSender, and not some shared PopupBuilder. This allows you to use it in closures and clear up some of the problems you were experiencing.

So those are the main things to keep in mind. I know this is a ton of stuff, but the more you use it, the easier it will become.



回答2:

You need to attach the functions to the object.

function PopupBuilder () {
    var _this = this;

    this.buildPopup = function () {} //works fine

    this.acceptChanges = function() { console.log(2);}
    this.clickHandler = function(event) { // works fine 
        console.log(_this); // _this doesn't overrides by child class _this. Should it? 
        //Look a the difference here: when you use _this.acceptChanges it prints 2 when you use this.acceptChanges() it prints 1 to the console.
        this.acceptChanges(event); // doesn't overrides by child class function as well

    }
}

function ToServerSender (text) {
    var _this = this;
    this.a = "hello";
    //this class overrides acceptChanges from PopupBuilder
    this.acceptChanges = function(event) {
        // send data to server
        console.log(1);
        console.log(_this);
    }
}

ToServerSender.prototype = new PopupBuilder();

var updateFile = new ToServerSender();
updateFile.buildPopup();

Look at the above code to see how it works.

When in PopupBuilder console.log prints _this it uses a reference to this in that scope (which contained the old acceptChanges. However in ToServerSender the scope has changed. This instance has other properties and is referred to by the this object in that scope. This example show the difference between using a reference to this and the object this.

On a side note: when using more than 1 instance of an object use function references to avoid memory issues.

example

    this.clickHandler = clickHandlerPopupBuilder
    function clickHandlerPopupBuilder(event) {
        console.log(_this);
        this.acceptChanges(event);          
    }