[Classes and Modules] Java-style class inheritance in JavaScript

[Classes and Modules] Java-style class inheritance in JavaScript

If you have experience developing Java or other strongly typed object-oriented languages like this, class members may look like this in your mind:

Instance field

They are instance-based attributes or variables that hold the state of individual objects.

Instance Method

They are methods shared by all instances of a class and called by each individual instance.

Class field

These attributes or variables belong to the class, not to an instance of the class.

Class method

These methods belong to a class, not to an instance of the class.

One difference between JavaScript and Java is that functions in JavaScript appear as values, and there is not much difference between methods and fields. If the property value is a function, then this property defines a method. Otherwise, it is just a common attribute or "field". Despite these differences, we can use JavaScript to simulate these four class member types in Java. Classes in JavaScript involve three different objects (see 9-1), and the properties of the three objects behave very similar to the following three class members:

Constructor object

As mentioned earlier, constructors (objects) define names for JavaScript classes. Any attributes added to this constructor object are class fields and methods (class methods if the attribute value is a function).

Prototype object

The properties of the prototype object are inherited by all instances of the class. If the property value of the prototype object is a function, the function is called as a method of the instance of the class.

Instance object

Each instance of a class is a separate object, and properties defined directly to that instance are not shared by all instance objects. Non-functional properties defined on an instance are actually the fields of the instance.

The steps to define classes in JavaScript can be reduced to a three-step algorithm. The first step is to define a constructor and set the instance properties that initialize the new object. The second step defines the method of the instance for the prototype object of the constructor. The third step defines class fields and class attributes for the constructor. We can encapsulate these three steps in a simple defineClass() function:

// A function to define a simple class 
function defineClass(constructor,     // Functions to set the properties of an instance 
                     methods,         // Instance method, copied to prototype 
                     statics)         // Class properties, copied into constructors
{ 
    if(methods) extend(constructor.prototype, methods);
    if(statics) extend(constructor, statics); 
    return constructor;
}

// This is another implementation of the Range class 
var SimpleRange 
    defineClass(function(f,t) { this.f = f; this.t = t; },
                { 
                    includes: function(x) { return this.f <=x && x < this.t;}, 
                    toString: function() { return this.f + "..." + this.t; }
                },    
                {   upto: function(t) { return new SimpleRange(0,t); } });

The following example defines a class with longer code. This defines a class that represents a complex number, and this code shows how to use JavaScript to simulate class members that implement Java style. The code in the following example does not use the defineClass() function above, but rather implements it "manually":

Example: Complex.js: Classes representing complex numbers
/* Complex.js:
 * This file defines the Complex class used to describe complex numbers
 * Recall that complex numbers are the sum of real and imaginary numbers, and imaginary numbers i are the square root of -1
 */

/*
 * This constructor defines the instance fields r and i for each instance it creates
 * These two fields hold the real part and the imaginary part of a complex number, respectively.
 * They are the state of the object
*/
function Complex(real, imaginary) {
    if (isNaN(real) || isNaN(imaginary))     // Ensure that both arguments are numbers
        throw new TypeError();               // Throw an error if not all numbers
    this.r = real;                           // real
    this.i = imaginary;                      // Imaginary part of a complex number
}

/*
 * Instance methods of classes are defined as function-valued properties of prototype objects
 * The methods defined here can be inherited by all instances and provide them with shared behavior
 * It is important to note that the JavaScript instance method must use the keyword this
 * To access the field of the instance
 */

// Current complex number object plus another complex number and returns a new complex number object after calculation and value
Complex.prototype.add = function (that) { 
    return new Complex(this.r + that.r, this.i + that.i);
};

// Multiply the current complex number by another complex number and return a new complex object after calculating the product
Complex.prototype.mul = function (that) {
    return new Complex(this.r * that.r - this.i * that.i, this.r * that.i + this.i * that.r); 
}

// Calculates the modulus of a complex number defined as the distance from the origin (0,0) to the complex plane 
Complex.prototype.mag = function () {
    return Math.sqrt(this.r * this.r + this.i * this.i); 
};

// Negative operation of complex numbers
Complex.prototype.neg = function () { 
    return new Complex(-this.r, -this.i);
};

// Converts a complex object to a string
Complex.prototype.toString = function () { 
    return "{" + this.r + "," + this.i + "}";
};

// Detects if the current complex object is equal to another complex value
Complex.prototype.equals = function (that) { 
    return that != null &&                      // Must be defined and cannot be null
    that.constructor === Complex &&             // And must be an instance of Complex
    this.r === that.r && this.i === that.i;     // And must contain the same value
};

/*
 * Class fields (such as constants) and class methods are directly defined as attributes of constructors
 * It is important to note that the keyword this is not typically used in class methods.
 * They only operate on their parameters
 */

// Some class fields are predefined here to help with complex arithmetic operations
// They are all named in uppercase to indicate that they are constants
//(In ECMAScript 5, you can also set the properties of these class fields to be read-only)
Complex.ZERO = new Complex(0, 0);
Complex.ONE = new Complex(1, 0);
Complex.I = new Complex(0, 1);

// This class method parses the string format returned by the toString method of the instance object into a Complex object 
// Or throw a type error exception
Complex.parse = function (s) {
    try { // Assumption resolution succeeded
        var m = Complex._format.exec(s); // Matching using regular expressions 
        return new Complex(parseFloat(m[1]), parseFloat(m[2]));
    } catch(x) { // Throw an exception if parsing fails
        throw new TypeError("Can't parse '" + s + "' as a complex number.");
};

// Defines the "private" field of the class, which is used in Complex.parse()
// The underline prefix indicates that it is used internally by the class and not part of the class's public API
Complex._format = /^\{([^,]+), ([^}]+)\}$/;

As you can see from the Complex class defined in the following example, we have used constructors, instance fields, instance methods, class fields, and class methods to look at this sample code:

var c = new Complex(2,3);         // Creating new objects using constructors
var d = new Complex(c.i,c.r);     // Instance properties using c
c.add(d).toString();	          // =>'{5,5}': using instance method
// This slightly more complex expression uses class methods and class fields
Complex.parse(c.toString());      // Convert c to string
 add(c.neg()).	              // Plus its negative number
 equals(Complex.ZERO)	      // The result should always be "zero"

Although JavaScript can emulate Java-style class members, there are many important features in Java that cannot be emulated in JavaScript classes. First, for instance methods of Java classes, instance fields can be used as local variables instead of referencing them with the keyword this. JavaScript cannot emulate this feature, but you can use the with statement to approximate it (which is not recommended):

Complex.prototype.toString = function() {
    with(this) {
        return "{" + r + "," + i + }";
    }
};

Can fields and methods be declared private in Java using final to indicate that they are private members and not visible outside the class? These keywords are not available in JavaScript. In the example below, some naming conventions are used to give some hints, such as which members cannot be modified (named in uppercase letters) and which members are invisible outside the class (named with the prefix underlined). Private attributes can be simulated using local variables in closures, and constant attributes can be implemented directly in ECMAScript5.

Keywords: Javascript Front-end

Added by magie on Wed, 24 Nov 2021 19:31:13 +0200