React learning notes: related knowledge of classes

Posted by keigowei on Wed, 16 Feb 2022 04:18:41 +0100

Learn from Ruan Yifeng's es6 tutorial. If you are interested, you can click here Check the original text.

1. Introduction

1-1, origin of class

The class of es6 can be regarded as a syntax sugar of the constructor. For example, there are the following constructors.

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);

It is changed to es6 as follows:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

The essence of these two writing methods is the same, but the writing method of es6 class is more concise and clear, more like the syntax of object-oriented programming.

Class also uses new, just like the constructor.

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

const b = new Bar();
b.doStuff() // "stuff"

The prototype attribute of the constructor continues to exist on the "class" of es6, and all methods defined in the class are above the prototype attribute of the class, including the constructor method.

class Point {
  constructor() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// Equivalent to

Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

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

1-2, constructor method

constructor() method is the default method of the class. It will be called automatically when an object instance is generated by the new command. A class must have a constructor() method. If there is no explicit definition, an empty constructor() method will be added by default.

class Point {
}

// Equivalent to
class Point {
  constructor() {}
}

The constructor() method will return the instance object (i.e. this) by default. We can also specify to return another object.

class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo
// false

If {constructor() returns another object, the instance created through new Foo() is not an instance of Foo class.

1-3, instance of class

The writing method of generating Class instances is the same as that of es5, and the new command is also used. If you forget to add new and call Class directly like a function, an error will be reported.

class Point {
  // ...
}
// report errors
var point = Point(2, 3);
// correct
var point = new Point(2, 3);

The attributes of an instance are defined on the prototype unless they are explicitly defined on itself (i.e. defined through this.xxx=xxx).

//Define class
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
var point = new Point(2, 3);
point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.hasOwnProperty('constructor') // false
point.__proto__.hasOwnProperty('toString') // true
point.__proto__.hasOwnProperty('constructor') // true

In the above code, the X and y attributes are through this XXX = XXX, so x and y belong to the instance itself. The constructor and toString are prototype objects (Point.prototype, point. _ proto_) Properties in.

1-4, value taking function (getter) and stored value function (setter)

You can set the value of the property (set) and the value of the property (get) stored in a function by accessing the property (set) of the function as follows:

let _name = 'tom'
class Person {
  constructor() {}
  get name(){
    console.log("name get")
    return _name
  }
  set name(v){
    console.log("name set")
    _name = v
  }
}

let p = new Person()
console.log(p.name) // name get,tom
p.name = 'jack' // name set
console.log(p.name) // name get,jack

1-5, force this in the function to point to the instance object

By default, if the method in the prototype is taken out and called separately, this in the method will not point to the instance of the class. The code is as follows:

class Person {
  constructor() {
    this.name = 'person object'
  }

  printThis(){
    console.log(this)
  }
}

let p = new Person()
p.printThis() // { name: 'person object' }

let otherObj = { name: 'other object' }
otherObj.printThis = p.printThis
otherObj.printThis() // { name: 'other object', printThis: [Function: printThis] }

If you want this in the function to still point to the instance when the extracted function is executed, there are two ways.

Method 1: use the bind() method to bind this forcibly.

class Person {
  constructor() {
    this.name = 'person object'
    // Use bind to bind this forcibly
    this.printThis = this.printThis.bind(this)
  }

  printThis(){
    console.log(this)
  }
}

let p = new Person()
p.printThis() // { name: 'person object', printThis: [Function: bound printThis] }

let otherObj = { name: 'other object' }
otherObj.printThis = p.printThis
otherObj.printThis() // { name: 'person object', printThis: [Function: bound printThis] }

Method 2: use arrow function to define.

The arrow function does not have its own separate this. Its internal this points to the outer this, and this designation is determined when defining the function, not during execution. You can use this feature of the arrow function to force this in the function to point to the instance object. The code is as follows:

class Person {
  constructor() {
    this.name = 'person object'
  }

  printThis = () => {
    console.log(this)
  }
}

let p = new Person()
p.printThis() // { name: 'person object', printThis: [Function: bound printThis] }
console.log(p.hasOwnProperty('printThis')) // true

let otherObj = { name: 'other object' }
otherObj.printThis = p.printThis
otherObj.printThis() // { name: 'person object', printThis: [Function: bound printThis] }

2. Static method

The methods defined in the class above exist in the prototype object of the class, and the instance of the class can call and trigger these methods. If we add the static keyword in front of a method, we can declare a static method. The static method does not exist in the prototype object and cannot be called through an instance. The static method is called through a class. The code is as follows:

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

If this is used in a static method, this refers to a class, not an instance.

class Foo {
  static bar() {
    this.baz();
  }
  static baz() {
    console.log('hello');
  }
  baz() {
    console.log('world');
  }
}

Foo.bar() // hello

This. Is executed in the static method bar Baz(), where this refers to the Foo class, which is equivalent to calling Foo Baz (), output "Hello". In addition, it can be seen from this example that static methods can have the same name as non static methods.

The static method of the parent class can be inherited by the child class.

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod() // 'hello'

Static methods can also be called through super.

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return super.classMethod() + ', too';
  }
}

Bar.classMethod() // "hello, too"

3. New writing method of instance attribute

In the above example, the instance attribute (the attribute owned by the instance object itself) is through this XXX = defined by XXX, for example:

class Person {
  constructor() {
    this.name = 'tom'
  }
}

In addition to this writing method, attributes can also be written at the top level of the class, for example:

class Person {
  name = 'tom'
}

The functions of the above two methods are the same, but the following method is more concise and clear. You can see the attributes of the instance object at a glance.

4. Static attributes

Static attribute refers to the attribute of Class itself, that is, Class Propname instead of the property defined on the instance object (this).

There are two ways to define static attributes. The first is the old method, which is written as follows:

class Foo {
}

Foo.prop = 1;
Foo.prop // 1

The new method uses static, which is written as follows:

// neographism
class Foo {
  static prop = 1;
}

In the above code, the static attribute of the old writing method is defined outside the class. After the entire class is generated, static attributes are generated. This makes it easy to ignore this static attribute, and does not comply with the code organization principle that the relevant code should be put together. In addition, the new writing method is explicit declaration, rather than assignment processing, which has better semantics.

5,extends

Class can inherit through the extends keyword, and let the subclass inherit the attributes and methods of the parent class. The writing method is as follows:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    // If a subclass declares a constructor() method, the constructor() method of the parent class must be called.
    // And you must call this XX = before XX assignment statement.
    super(x, y);
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // Call the toString() of the parent class
  }
}

In the above example, Point is the parent class and ColorPoint is the subclass. The subclass inherits all the properties and methods of the parent class and overrides the toString method.

The constructor and toString methods in ColorPoint use the super keyword. The super keyword can be used as either a function or an object. The functions of these two usages are completely different.

  • The super keyword in constructor indicates the constructor of the parent class. It is called here to create an instance object of the parent class. es6 the constructor of the sub class must execute the super function once. The super() executed here is equivalent to a.prototype constructor. call(this).
  • In the toString method, super is used as an object. In common methods, it points to the prototype object of the parent class; In a static method, point to the parent class.

ES6 specifies that subclasses must call super() in the constructor() method, otherwise they will report errors. This is because the subclass's own this object must first be molded through the constructor of the parent class to obtain the same instance properties and methods as the parent class, and then process it to add the subclass's own instance properties and methods. If you do not call the super method, the subclass will not get its own this object.

Why does the constructor of subclasses have to call super()? The reason is that the inheritance mechanism of ES6 is completely different from that of ES5. The inheritance mechanism of ES5 is to first create an instance object of an independent subclass, and then add the methods of the parent class to the object, that is, "instance first, inheritance last". The inheritance mechanism of ES6 is to first add the attributes and methods of the parent class to an empty object, and then take the object as an instance of the child class, that is, "inheritance first, instance last". This is why the inheritance of ES6 must first call the super () method, because this step will generate a this object that inherits the parent class. Without this step, the parent class cannot be inherited.

Topics: Front-end React