This is the 11th day of my participation in the August More Text Challenge
-
The first paperThe basic syntax of Class
The Class inheritance
Introduction to the
Classes can be inherited through the extends keyword, which is much cleaner and more convenient than ES5’s inheritance through modified prototype chains.
class Point {}class ColorPoint extends Point {}Copy the code
The above code defines a ColorPoint class that inherits all the properties and methods of the Point class through the extends keyword. But since no code is deployed, the two classes are exactly the same, duplicating a Point class. Next, we add code inside ColorPoint.
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // Call parent constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // Call the parent toString()}}Copy the code
In the above code, the super keyword appears in both the constructor and toString methods, where it represents the parent’s constructor, which is used to create a new parent’s this object.
Subclasses must call the super method from the constructor method or they will get an error when creating a new instance. This is because the subclass’s this object must be molded by the parent’s constructor to get the same instance attributes and methods as the parent, and then processed to add the subclass’s instance attributes and methods. If you don’t call super, your subclasses don’t get this.
class Point { / *... * / }
class ColorPoint extends Point {
constructor(){}}let cp = new ColorPoint(); // ReferenceError
Copy the code
In the code above, ColorPoint inherits Point from its parent class, but its constructor does not call super, causing an error when creating a new instance.
ES5 inheritance essentially creates an instance object of the subclass, this, and then adds the Parent class’s methods to this (parent.apply (this)). ES6 has a completely different inheritance mechanism, essentially adding the properties and methods of the superclass instance object to this (so the super method must be called first), and then modifying this with the constructor of the subclass.
If the subclass does not define a constructor method, this method is added by default, as shown below. That is, any subclass has a constructor method whether or not it is explicitly defined.
class ColorPoint extends Point {}/ / is equivalent to
class ColorPoint extends Point {
constructor(. args) {
super(...args);
}
}
Copy the code
Another thing to note is that in the constructor of a subclass, the this keyword can only be used after super is called, otherwise an error will be reported. This is because subclass instances are built on superclass instances, and only super methods can call superclass instances.
class Point {
constructor(x, y) {
this.x = x;
this.y = y; }}class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; / / right}}Copy the code
In the code above, the subclass’s constructor method uses the this keyword without calling super, which results in an error and is correct after the super method.
Below is the code to generate an instance of a subclass.
let cp = new ColorPoint(25.8.'green');
cp instanceof ColorPoint // true
cp instanceof Point // true
Copy the code
In the above code, the instance object CP is an instance of both ColorPoint and Point classes, which is exactly the same as ES5 behavior.
Finally, static methods of the parent class are also inherited by subclasses.
class A {
static hello() {
console.log('hello world'); }}class B extends A {
}
B.hello() // hello world
Copy the code
In the code above, hello() is A static method of class A, from which B inherits as well as A’s static method.
Object.getPrototypeOf()
The object.getProtoTypeof method can be used to get a parent class from a subclass.
Object.getPrototypeOf(ColorPoint) === Point
// true
Copy the code
Therefore, you can use this method to determine whether a class inherits from another class.
The super keyword
The super keyword can be used as either a function or an object. In both cases, it’s used quite differently.
In the first case, super, when called as a function, represents the constructor of the parent class. ES6 requires that the constructor of a subclass must execute the super function once.
class A {}
class B extends A {
constructor() {
super();
}
}
Copy the code
In the code above, super() in the constructor of subclass B stands for calling the constructor of the parent class. This is necessary, otherwise the JavaScript engine will report an error.
Note that super although represents the parent class constructor, but returns the instance of the subclass B, namely the inside of the super this refers to the instance of B so super () is equivalent to Amy polumbo rototype here. The constructor. Call (this).
class A {
constructor() {
console.log(new.target.name); }}class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
Copy the code
In the above code, new.target points to the function currently executing. As you can see, when super() executes, it points to the constructor of subclass B, not the constructor of superclass A. That is, the this inside super() refers to B.
When used as a function, super() can only be used in the constructor of a subclass, otherwise an error will be reported.
class A {}
class B extends A {
m() {
super(a);/ / an error}}Copy the code
In the code above, using super() in the m method of class B causes a syntax error.
In the second case, super as an object, in a normal method, points to a prototype object of the parent class; In static methods, point to the parent class.
class A {
p() {
return 2; }}class B extends A {
constructor() {
super(a);console.log(super.p()); / / 2}}let b = new B();
Copy the code
In the code above, super.p() in subclass B uses super as an object. In this case, super refers to a.prototype in normal methods, so super.p() equals a.prototype.p ().
It is important to note that since super refers to the parent’s prototype object, methods or properties defined on the parent instance cannot be called by super.
class A {
constructor() {
this.p = 2; }}class B extends A {
get m() {
return super.p; }}let b = new B();
b.m // undefined
Copy the code
In the code above, p is an attribute of an instance of superclass A. super.p does not refer to it.
Super can be fetched if the property is defined on a stereotype object of the parent class.
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super(a);console.log(super.x) / / 2}}let b = new B();
Copy the code
In the code above, the attribute x is defined above a.protoType, so super.x can take its value.
ES6 states that when a method of a parent class is called through super in a subclass normal method, this inside the method refers to the current subclass instance.
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x); }}class B extends A {
constructor() {
super(a);this.x = 2;
}
m() {
super.print(); }}let b = new B();
b.m() / / 2
Copy the code
In the code above, super.print() calls a.prototype.print (), but the this inside a.prototype.print () points to an instance of subclass B, resulting in 2 instead of 1. That is, super.print.call(this) is actually executed.
Since this refers to the subclass instance, if you assign a value to a property via super, which is this, the assigned property becomes the property of the subclass instance.
class A {
constructor() {
this.x = 1; }}class B extends A {
constructor() {
super(a);this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); / / 3}}let b = new B();
Copy the code
In the code above, assigning super.x to 3 is the same as assigning this.x to 3. When super.x is read, a.prototype. x is read, so return undefined.
If super is used as an object in a static method, then super refers to the parent class, not the parent class’s prototype object.
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg); }}class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
var child = new Child();
child.myMethod(2); // instance 2
Copy the code
In the code above, super refers to the parent class in static methods and to the parent class’s prototype object in normal methods.
Also, when a method of a parent class is called through super in a static method of a subclass, the this inside the method refers to the current subclass, not the instance of the subclass.
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x); }}class B extends A {
constructor() {
super(a);this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() / / 3
Copy the code
In the code above, in the static method B.m, super.print points to the static method of the parent class. This in this method refers to B, not an instance of B.
Note that when you use super, you must explicitly specify whether to use it as a function or as an object, otherwise an error will be reported.
class A {}
class B extends A {
constructor() {
super(a);console.log(super); / / an error}}Copy the code
In the code above, the super in console.log(super) is not clear if it is used as a function or as an object, so the JavaScript engine will report an error when parsing the code. At this point, if the data type of super is clearly stated, no errors will be reported.
class A {}
class B extends A {
constructor() {
super(a);console.log(super.valueOf() instanceof B); // true}}let b = new B();
Copy the code
In the code above, super.valueof () indicates that super is an object, so no error is reported. Also, since super makes this refer to an instance of B, super.valueof () returns an instance of B.
Finally, since objects always inherit from other objects, the super keyword can be used in any object.
var obj = {
toString() {
return "MyObject: " + super.toString(); }}; obj.toString();// MyObject: [object Object]
Copy the code
Class prototype property and __proto__ property
In most browsers’ ES5 implementations, each object has a __proto__ attribute that points to the prototype attribute of the corresponding constructor. Class is the syntactic sugar of the constructor and has both the Prototype and __proto__ attributes, so there are two inheritance chains.
(1) The __proto__ attribute of a subclass, which indicates the constructor’s inheritance, always points to the parent class.
(2) The prototype attribute’s __proto__ attribute, indicating method inheritance, always points to the prototype attribute of the parent class.
class A {}class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
Copy the code
In the code above, the __proto__ attribute of subclass B points to parent A, and the __proto__ attribute of subclass B’s Prototype attribute points to the prototype attribute of parent A.
The result is that class inheritance is implemented according to the following pattern.
class A {}class B {}// Instance B inherits instance A
Object.setPrototypeOf(B.prototype, A.prototype);
// B inherits A's static attributes
Object.setPrototypeOf(B, A);
const b = new B();
Copy the code
An implementation of the Object.setPrototypeOf method is described in the chapter Extension of Objects.
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
Copy the code
Therefore, we get the result above.
Object.setPrototypeOf(B.prototype, A.prototype);
/ / is equivalent to
B.prototype.__proto__ = A.prototype;
Object.setPrototypeOf(B, A);
/ / is equivalent to
B.__proto__ = A;
Copy the code
The two inheritance chains can be understood as follows: as an object, the prototype (__proto__ attribute) of subclass (B) is the parent (A); As a constructor, the prototype object (prototype property) of subclass (B) is an instance of the prototype object (Prototype property) of the parent class.
B.prototype = Object.create(A.prototype);
/ / is equivalent to
B.prototype.__proto__ = A.prototype;
Copy the code
The extends keyword can be followed by many types of values.
class B extends A {}Copy the code
A of the above code, as long as it is A function with the prototype property, can be inherited by B. Since all functions have the prototype attribute (except the function. prototype Function), A can be any Function.
Now, two cases are discussed. First, subclasses inherit from the Object class.
class A extends Object {
}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
Copy the code
In this case, A is actually A copy of the constructor Object, and an instance of A is an instance of Object.
In the second case, there is no inheritance.
class A {
}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
Copy the code
In this case, as A base class (i.e. without any inheritance), A is A normal Function, so it inherits function.prototype directly. However, A returns an empty Object (that is, an Object instance), so A.prototype.__proto__ points to the prototype property of the constructor (Object).
The __proto__ attribute of the instance
The __proto__ attribute of the __proto__ attribute of the subclass instance points to the __proto__ attribute of the parent class instance. That is, the prototype of a subclass is the prototype of its parent class.
var p1 = new Point(2.3);
var p2 = new ColorPoint(2.3.'red');
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
Copy the code
In the code above, ColorPoint inherits Point, causing the prototype of the former prototype to be the prototype of the latter.
Therefore, the behavior of a subclass instance can be modified through the __proto__.__proto__ attribute of a subclass instance.
p2.__proto__.__proto__.printName = function () {
console.log('Ha');
};
p1.printName() // "Ha"
Copy the code
The above code adds a method to the Point class on ColorPoint instance P2, which affects Point instance P1.
Inheritance of the native constructor
Native constructors are the language’s built-in constructors, typically used to generate data structures. ECMAScript’s native constructors look something like this.
- Boolean()
- Number()
- String()
- Array()
- Date()
- Function()
- RegExp()
- Error()
- Object()
Previously, these native constructors could not be inherited, for example, defining a subclass of Array.
function MyArray() {
Array.apply(this.arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true.configurable: true.enumerable: true}});Copy the code
The above code defines a MyArray class that inherits Array. However, the behavior of this class is completely different from Array.
var colors = new MyArray();
colors[0] = "red";
colors.length / / 0
colors.length = 0;
colors[0] // "red"
Copy the code
This happens because subclasses cannot get the internal properties of the native constructor, either through array.apply () or by assigning them to the prototype object. The native constructor ignores this passed in by apply. That is, the this of the native constructor cannot be bound, resulting in no internal attributes.
ES5 creates the instance object this of the subclass first, and then adds the attributes of the parent class to the subclass. Because the internal attributes of the parent class cannot be obtained, it cannot inherit the native constructor. For example, the Array constructor has an internal property [[DefineOwnProperty]] that is used to update the Length property when defining a new property. This internal property is not available in the subclass, causing the subclass’s Length property to behave improperly.
In the following example, we want an ordinary object to inherit from an Error object.
var e = {};
Object.getOwnPropertyNames(Error.call(e))
// [ 'stack' ]
Object.getOwnPropertyNames(e)
/ / []
Copy the code
In the above code, we want to write error.call (e) so that the ordinary object E has the instance property of the Error object. However, error.call () completely ignores the first argument passed in and instead returns a new object, with e itself unchanged. This proves that the err.call (e) notation does not inherit from the native constructor.
ES6 allows subclasses to be inherited from native constructors, because ES6 creates an instance object of the parent class, this, and then decorates this with the constructor of the subclass, making all behavior of the parent class inheritable. Here is an example of an Array inheritance.
class MyArray extends Array {
constructor(. args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length / / 1
arr.length = 0;
arr[0] // undefined
Copy the code
The above code defines a MyArray class that inherits the Array constructor, so it can generate an instance of an Array from MyArray. This means that ES6 can customize subclasses of native data structures such as Array, String, and so on, which ES5 cannot.
The example above also shows that the extends keyword can be used to extend not only classes but also native constructors. Therefore, you can define your own data structure based on the native data structure. Here is an array that defines a version function.
class VersionedArray extends Array {
constructor() {
super(a);this.history = [[]];
}
commit() {
this.history.push(this.slice());
}
revert() {
this.splice(0.this.length, ... this.history[this.history.length - 1]); }}var x = new VersionedArray();
x.push(1);
x.push(2);
x / / [1, 2]
x.history / / [[]]
x.commit();
x.history / / [[], [1, 2]]
x.push(3);
x / / [1, 2, 3]
x.history / / [[], [1, 2]]
x.revert();
x / / [1, 2]
Copy the code
In the code above, VersionedArray uses the COMMIT method to generate a version snapshot of its current state and store it in the history property. The Revert method is used to reset an array to the most recently saved version. In addition, VersionedArray is still a normal array on which all native array methods can be called.
Here is an example of a custom Error subclass that can be used to customize the behavior when reporting an Error.
class ExtendableError extends Error {
constructor(message) {
super(a);this.message = message;
this.stack = (new Error()).stack;
this.name = this.constructor.name; }}class MyError extends ExtendableError {
constructor(m) {
super(m); }}var myerror = new MyError('ll');
myerror.message // "ll"
myerror instanceof Error // true
myerror.name // "MyError"
myerror.stack
// Error
// at MyError.ExtendableError
/ /...
Copy the code
Note that subclasses that inherit Object have a behavior difference.
class NewObj extends Object{
constructor(){
super(...arguments);
}
}
var o = new NewObj({attr: true});
o.attr === true // false
Copy the code
In the code above, NewObj inherits Object, but cannot pass an argument to its parent Object using the super method. This is because ES6 has changed the behavior of Object constructors by specifying that Object constructors ignore arguments when it is discovered that an Object method is not called with the form new Object().
Implementation of Mixin patterns
A Mixin is a new object composed of multiple objects with interfaces for its constituent members. Its simplest implementation is as follows.
const a = {
a: 'a'
};
const b = {
b: 'b'
};
constc = {... a, ... b};// {a: 'a', b: 'b'}
Copy the code
In the code above, the C object is a composite of the A object and the B object, with interfaces for both.
Here is a more complete implementation that “mixes” interfaces from multiple classes into another class.
function mix(. mixins) {
class Mix {
constructor() {
for (let mixin of mixins) {
copyProperties(this.new mixin()); // Copy the instance properties}}}for (let mixin of mixins) {
copyProperties(Mix, mixin); // Copy static properties
copyProperties(Mix.prototype, mixin.prototype); // Copy the stereotype properties
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if( key ! = ='constructor'&& key ! = ='prototype'&& key ! = ='name'
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc); }}}Copy the code
The mix function of the above code can combine multiple objects into a single class. When used, simply inherit the class.
class DistributedEdit extends mix(Loggable.Serializable) {
// ...
}
Copy the code
-
The first paperThe basic syntax of Class