The origin of the class
The traditional way to generate instance objects in the JavaScript language is through constructors. Here’s an example.
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ') ';
};
var p = new Point(1.2);
Copy the code
This is very different from traditional object-oriented languages such as C++ and Java, and can easily confuse new programmers.
ES6 provides a more traditional language approach, introducing the concept of classes as templates for objects. With the class keyword, you can define a class.
Basically, ES6 classes can be seen as a syntactic candy that does most of what ES5 does. The new class writing method simply makes object prototype writing clearer and more like object-oriented programming syntax. The above code is rewritten using ES6 class, as follows.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ') '; }}Copy the code
The above code defines a “class”. You can see that it has a constructor() method, which is the constructor, and the this keyword represents the instance object. This new Class notation is essentially the same as the ES5 constructor Point at the beginning of this article.
In addition to the constructor, the ◾ Point class defines a toString() method. Note that when defining the toString() method, you do not need the function keyword in front of it, just put the function definition in it. In addition, methods do not need to be separated by commas.
ES6 classes can be thought of as just another way of writing constructors.
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
Copy the code
The above code shows that the data type of a class is a function, and the class itself points to a constructor.
When used, the new command is used directly on the class, exactly as the constructor is used.
class Bar {
doStuff() {
console.log('stuff'); }}const b = new Bar();
b.doStuff() // "stuff"
Copy the code
The prototype property of the constructor continues on the ES6 “class”. In fact, all methods of a class are defined on the prototype property of the class.
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...}}/ / is equivalent to
Point.prototype = {
constructor() {},
toString() {},
toValue(){}};Copy the code
The constructor(), toString(), and toValue() methods are defined in point.prototype.
Therefore, calling a method on an instance of a class is actually calling a method on the prototype.
class B {}
const b = new B();
b.constructor === B.prototype.constructor // true
Copy the code
In the code above, B is an instance of class B, and its constructor() method is the constructor() method of the prototype of class B.
Since the class’s methods are defined on top of the Prototype object, new methods of the class can be added to the Prototype object. The object.assign () method makes it easy to add multiple methods to a class at once.
class Point {
constructor(){
// ...}}Object.assign(Point.prototype, {
toString(){},
toValue(){}});Copy the code
The Prototype object’s constructor() property, which points directly to the “class” itself, is consistent with ES5 behavior.
Point.prototype.constructor === Point // true
Copy the code
In addition, all methods defined inside a class are non-enumerable.
class Point {
constructor(x, y) {
// ...
}
toString() {
// ...}}Object.keys(Point.prototype)
/ / []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
Copy the code
In the code above, the toString() method is defined internally by the Point class and is not enumerable. This is not consistent with ES5 behavior.
var Point = function (x, y) {
// ...
};
Point.prototype.toString = function () {
// ...
};
Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
Copy the code
The above code is written in ES5, where the toString() method is enumerable.
Note: In JavaScript, the properties of an object are enumerable and non-enumerable. Enumerability determines whether this attribute can be used for… In find traversal to.
Enumerable attributes are those with an internal “enumerable” flag set to true, which defaults to true for attributes initialized via direct assignment and attributes, and false for attributes defined via object.defineProperty, etc. Enumerable properties are available via for… The in loop iterates (unless the property name is a Symbol).
The enumerability of a property affects the results of three functions:
-
The for… in
-
Object.keys()
-
JSON.stringify
Constructor
Constructor is a special method for creating and initializing objects created by a class.
class Polygon {
constructor() {
this.name = 'Polygon'; }}const poly1 = new Polygon();
console.log(poly1.name);
// expected output: "Polygon"
Copy the code
The constructor() method is the default method of the class and is automatically called when an object instance is generated using the new command. A class must have a constructor() method; if it is not explicitly defined, an empty constructor() method is added by default.
class Point {}/ / is equivalent to
class Point {
constructor(){}}Copy the code
In the above code, we define an empty class Point, to which the JavaScript engine automatically adds an empty constructor() method.
The constructor() method returns the instance object (that is, this) by default; you can specify that another object should be returned.
class Foo {
constructor() {
return Object.create(null); }}new Foo() instanceof Foo
// false
Copy the code
In the code above, the constructor() function returns a brand new object, resulting in the instance object not being an instance of class Foo.
The class must be called with new or an error will be reported. This is a major difference from normal constructors, which can be executed without new.
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
Copy the code
There can be only one special method in a class called “constructor”. Multiple constructor methods in a class will raise a SyntaxError.
The super keyword can be used in a constructor to call the constructor of a parent class.
If no constructor is explicitly specified, the default constructor method is added.
If you do not specify a constructor method, a default constructor is used.
◾ use the constructor method
class Square extends Polygon {
constructor(length) {
// Here it calls the parent constructor and lengths to the "width" and "height" supplied to Polygon
super(length, length);
// Note: In derived classes, you must first call super() to use "this".
// Ignoring this will result in a reference error.
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
set area(value) {
// Note: this.area = value cannot be used
// Otherwise the loop call setter method will burst the stack
this._area = value; }}Copy the code
There are two important points here:
- Note: In derived classes, you must first call super() to use “this”. Ignoring this will result in a reference error.
- Note: in
set area(value)
Cannot be used inthis.area = value
Otherwise, the loop call setter method will burst the stack
◾ Default constructor
As mentioned earlier, if no constructor is specified, the default constructor is used. For base classes, the default constructor is:
constructor() {}
Copy the code
Copy to Clipboard
For derived classes, the default constructor is:
constructor(. args) {
super(... args); }Copy the code
Class instantiation
Class must be instantiated by the new keyword.
class Example {}
let exam1 = Example();
// Class constructor Example cannot be invoked without 'new'
Copy the code
◾ Instantiate the object
All instances of the class share a stereotype object.
class Example {
constructor(a, b) {
this.a = a;
this.b = b;
console.log('Example');
}
sum() {
return this.a + this.b; }}let exam1 = new Example(2.1);
let exam2 = new Example(3.1);
console.log(exam1._proto_ == exam2._proto_); // true
exam1._proto_.sub = function () {
return this.a - this.b;
}
console.log(exam1.sub()); / / 1
console.log(exam2.sub()); / / 2
Copy the code
In the code above, exam1 and exam2 are both examples of Example. Prototype, so the __proto__ attribute is equal.
This also means that methods can be added to a “class” through the __proto__ attribute of an instance.
__proto__ is not a feature of the language itself, it is a private attribute added by various manufacturers. Although many modern browsers provide this private attribute in the JS engine, it is still not recommended to use this attribute in production to avoid environment dependence. In production, we can use the Object.getProtoTypeof method to get the prototype of the instance Object and then add methods/properties to the prototype.
Rewriting a prototype using the __proto__ attribute of an instance must be done with great caution and is not recommended because it changes the original definition of the “class”, affecting all instances.
Getters and setters
You can use the get and set keywords inside a “class” to set the store and value functions for an attribute and intercept the access behavior of that attribute.
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value); }}let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
Copy the code
In the above code, the prop property has corresponding store and value functions, so the assignment and read behavior are customized.
The store and value functions are set on the Descriptor object of the property.
class CustomHTMLElement {
constructor(element) {
this.element = element;
}
get html() {
return this.element.innerHTML;
}
set html(value) {
this.element.innerHTML = value; }}var descriptor = Object.getOwnPropertyDescriptor(
CustomHTMLElement.prototype, "html"
);
"get" in descriptor // true
"set" in descriptor // true
Copy the code
In the above code, the store and value functions are defined on the description object of the HTML attribute, which is exactly the same as in ES5.
class Example {
constructor(a, b) {
this.a = a; // Call the set method on instantiation
this.b = b;
}
get a() {
console.log('getter');
return this.a;
}
set a(a) {
console.log('setter');
this.a = a; // self recursive call}}let exam = new Example(1.2); // Output setter continuously, resulting in RangeError
class Example1 {
constructor(a, b) {
this.a = a;
this.b = b;
}
get a() {
console.log('getter');
return this._a;
}
set a(a) {
console.log('setter');
this._a = a; }}let exam1 = new Example1(1.2); // outputs only setters, no getters
console.log(exam1._a); // 1, can be accessed directly
Copy the code
◾ getter cannot appear alone
class Example {
constructor(a) {
this.a = a;
}
get a() {
return this.a; }}let exam = new Example(1);
// Uncaught TypeError: Cannot set property a of #<Example> which has only a getter
Copy the code
◾ Getters and setters must appear at the same level
class Father {
constructor() {}
get a() {
return this._a; }}class Child extends Father {
constructor() {
super(a); }set a(a) {
this._a = a; }}let test = new Child();
test.a = 2;
console.log(test.a); // undefined
Copy the code
Correctly written: Declare the class when it is createdget
和set
, or theget
和set
All in subclasses
class Father1 {
constructor() {}
// or both in subclasses
get a() {
return this._a;
}
set a(a) {
this._a = a; }}class Child1 extends Father1 {
constructor() {
super();
}
}
let test1 = new Child1();
test1.a = 2;
console.log(test1.a); / / 2
Copy the code
Static method static
The class uses the static keyword to define static methods. Static methods cannot be called on an instance of a class, but should be called from the class itself. These are usually utility methods, such as the ability to create or clone objects.
A class is the prototype of an instance, and all methods defined in a class are inherited by the instance. If you prefix a method with the static keyword, it means that the method is not inherited by the instance, but is called directly from the class. This is called a “static method”.
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
Copy the code
In the code above, the static keyword precedes the classMethod method of class Foo, indicating that the method is a static method that can be called directly on class Foo (foo.classmethod ()), not on an instance of class Foo. If a static method is called on an instance, an error is thrown indicating that the method does not exist.
◾ Note that if the static method contains the this keyword, this refers to the class, not the instance.
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar() // hello
Copy the code
In the code above, the static method bar calls this.baz, where this refers to class Foo, not an instance of Foo, the same as calling foo. baz. Also, as you can see from this example, static methods can have the same name as non-static methods.
◾ static methods of the parent class that can be inherited by subclasses.
class Foo {
static classMethod() {
return 'hello'; }}class Bar extends Foo {
}
Bar.classMethod() // 'hello'
Copy the code
In the code above, the parent class Foo has a static method that the subclass Bar can call.
◾ static methods can also be called from super objects.
class Foo {
static classMethod() {
return 'hello'; }}class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
Copy the code
The keyword super
The super keyword is used to access and call functions on the parent object of an object.
When used in constructors, the super keyword appears alone and must be used before the this keyword. The super keyword can also be used to call functions on parent objects.
◾ calls the constructor of the parent class
class Polygon {
constructor(height, width) {
this.name = 'Rectangle';
this.height = height;
this.width = width;
}
sayName() {
console.log('Hi, I am a '.this.name + '. ');
}
get area() {
return this.height * this.width;
}
set area(value) {
this._area = value; }}class Square extends Polygon {
constructor(length) {
this.height; // This. heigh t will say ReferenceError because super needs to be called first!
// Here, it calls the constructor of the parent class,
// As the height, width of Polygon
super(length, length);
// Note: In derived classes, super() must be called before you can use 'this'.
// Ignore this, which will result in a reference error.
this.name = 'Square'; }}Copy the code
◾ calls a static method on the parent class
class Rectangle {
constructor() {}
static logNbSides() {
return 'I have 4 sides'; }}class Square extends Rectangle {
constructor() {}
static logDescription() {
return super.logNbSides() + ' which are all equal';
}
}
Square.logDescription(); // 'I have 4 sides which are all equal'
Copy the code
◾ subclass constructor must have super in the method and must come before this.
Here are two errors:
class Father {
constructor(){}}class Child extends Father {
constructor(){}}let test = new Child();
// Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Copy the code
class Father {
constructor(){}}class Child extends Father {
or
constructor(a) {
this.a = a;
super();
}
}
let test = new Child();
// Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Copy the code
◾ calls the parent constructor, which can only appear in the constructor of a subclass.
class Father {
test() {
return 0;
}
static test1() {
return 1; }}class Child extends Father {
constructor() {
super();
}
}
class Child1 extends Father {
test2() {
super(a);// Uncaught SyntaxError: 'super' keyword unexpected
// here}}Copy the code
◾ calls the superclass method, super as an object, pointing to the prototype object of the superclass in normal methods, or to the superclass in static methods.
class Child2 extends Father {
constructor(){
super(a);// Call the parent ordinary method
console.log(super.test()); / / 0
}
static test3(){
// Call the parent static method
return super.test1+2;
}
}
Child2.test3(); / / 3
Copy the code
Inheritance extends
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.
The ◾ subclass must call the super method from the constructor method or it 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.
There is no variable promotion
Unlike ES5, there is no hoist for the class.
new Foo(); // ReferenceError
class Foo {}
Copy the code
In the code above, class Foo is used first and defined later, which is an error because ES6 does not elevate the class declaration to the header. The reason for this rule is related to inheritance, which is discussed below, and the need to ensure that subclasses are defined after their parents.
{
let Foo = class {};
class Bar extends Foo {
}
}
Copy the code
The above code does not report an error because Foo is already defined when Bar inherits from Foo. However, if there is a class promotion, the above code will report an error because the class is promoted to the header, while the let command is not promoted, resulting in Bar inheriting Foo before Foo is defined.
reference
- Class – JavaScript | MDN (mozilla.org)
- Introduction to ECMAScript 6 (ES6) standards
- ES6 Class Class
- Enumerable and non-enumerable properties in JavaScript