Lost reference to self when using KnockoutJS and Simple Class Inheritance

后端 未结 2 1150
误落风尘
误落风尘 2021-01-28 09:07

I am using John Resig\'s \"Simple JavaScript Inheritance\" to create a class that can be inherited. I am also using KnockoutJS for computed observables. The problem comes in t

2条回答
  •  野趣味
    野趣味 (楼主)
    2021-01-28 09:43

    I've never had any experience with KnockoutJS. However I'm well versed with inheritance in JavaScript and I frown upon John Resig's "Simple JavaScript Inheritance" pattern (primarily because it doesn't have private variables). Instead I prefer using my own class pattern. I think you might find it interesting:

    window.mynamespace.myclass = new Class(function (uber) {
        var self = this;
    
        function constructor() {
        }
    
        this.someProperty = ko.observable(10);
    
        this.someComputedProperty = ko.computed(function () {
            return self.someProperty();
        });
    
        return constructor;
    });
    

    You may also use your method and simply skip creating self. Since the class is not created from an object you may use this within your class definition (something you can't do in John Resig's "Simple JavaScript Inheritance" pattern):

    window.mynamespace.myclass = new Class(function (uber) {
        function constructor() {
        }
    
        this.someProperty = ko.observable(10);
    
        this.someComputedProperty = ko.computed(function () {
            return self.someProperty();
        }, this);
    
        return constructor;
    });
    

    You may also add methods directly to window.mynamespace.myclass.prototype. That way instead of returning self.someProperty() you may return myclass.prototype.someProperty(). Feel free to ask me if you need any help with my class pattern.

    Edit:

    The Class constructor has two parameters: a function defining the class and an optional base class to derive from. The first argument (i.e. the function) must return another function which is the constructor of the class (similar to a C++ or Java constructor).

    Let's create a simple Rectangle class:

    var Rectangle = new Class(function () {
        // width and height are private variables
        var width;
        var height;
    
        // the class constructor accepts two arguments
        function constructor(length, breadth) {
            // save the parameters
            width = length;
            height = breadth;
        }
    
        // area is a public function
        this.area = function () {
            // return the area of the rectangle
            return width * height;
        };
    
        return constructor; // always remember to return the constructor
    });
    

    Now you can create instances of the class Rectangle as demonstrated in this fiddle.

    Now let's do something more fun - inheritance. The second parameter of the Class constructor is the base class to derive from. Let's create a class Square which derives from the class Rectangle.

    // notice that the class definition function accepts a parameter called uber
    var Square = new Class(function (uber) {
        // return the constructor of the class Square
        return function (side) {
            // call the base class constructor - uber
            uber(side, side);
        };
    }, Rectangle); // Square derives from Rectangle
    

    This is where things get interesting. Okay so we have 4 types of data members in this class pattern: private, public, shared, and static. We have already seen private and public data members.

    Shared data members are those properties defined on the prototype of a class. They are shared by all instances of the class.

    Static data members are those properties defined on the class itself. They are not inherited by the instances of the class.

    In the above example when Square derives from Rectangle it inherits all the shared and static data members of Rectangle. In fact we may also define a new shared or static data member on Rectangle after Square is defined and it will still be inherited by Square. Let's demonstrate this concept using an example:

    alert(Square.staticDataMember); // staticDataMember is undefined
    Rectangle.staticDataMember = "It works!"; // define staticDataMember on Rectangle
    alert(Square.staticDataMember); // staticDataMember is inherited from Rectangle
    

    The above is for static data members. Let's see the same for shared data members:

    var square = new Square(5); // create an instance of Square
    alert(square.sharedDataMember); // sharedDataMember is undefined
    Rectangle.prototype.sharedDataMember = 0; // define sharedDataMember on Rectangle
    alert(square.sharedDataMember); // sharedDataMember is inherited from Rectangle
    

    You may see the output of the above program in the following fiddle.

    That's cool, but what about private and public data members? How are they inherited? Well, private and public data members are not inherited when we create a new class. They are inherited when create a new instance of a class. This is because different instances of the same class have different private and public data members.

    When we create an instance of a derived class (like Square) we don't inherit the private and public data members of it's base class (i.e. Rectangle) until we call the base class constructor (i.e. uber). Only when we call the base class constructor are the private and public data members inherited (actually only the public data members are inherited - the others are called private for a reason):

    var Square = new Class(function (uber) {
        return function (side) {
            alert(this.area); // area is undefined
            uber(side, side); // inherit public members from an instance of Rectangle
            alert(this.area); // area is now defined
        };
    }, Rectangle);
    

    You may see the output of the above program in the following fiddle.

    Now let's create another class for kicks, and while we're at it we'll also demonstrate multilevel inheritance:

    var Cube = new Class(function (uber) {
        var side; // the length of a side of the cube
    
        function constructor() {
            side = arguments[0]; // save the first argument passed to the constructor
            uber = uber(side); // call the base constructor and save its instance
        }
    
        this.area = function () {
            return 6 * uber.area(); // return the surface area of the cube
        };
    
        this.volume = function () {
            return side * uber.area(); // return the volume of the cube
        };
    
        return constructor; // remember to return the constructor
    }, Square); // derive from Square
    

    This is something new. Here we created a new area function which shadowed the area function defined in Rectangle, but what if we wanted to access the base class methods from the derived class?

    Well, when we call the base class constructor (i.e. uber) it returns the instance of the base class. Since we don't need the base class constructor anymore we save the instance as uber. Then we may call the base class area method using uber.area as seen in the area and volume functions.

    You may see the output of the above program in the following fiddle.

    Thus you may see that this class pattern is much more powerful than John Resig's "Simple JavaScript Inheritance" pattern, and the Class constructor is only 47 lines of code (without being minified).

提交回复
热议问题