ES6 knows how to implement inheritance by class

Introduction

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

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
let p = new point(1,2)

constructor method

  • The constructor() method is the default method of the class. This method is automatically called when an object instance is generated through the new command.
class Point {
}

// 等同于
class Point {
  constructor() {}
}
//上面代码中,定义了一个空的类Point,JavaScript 引擎会自动为它添加一个空的constructor()方法。
  • The constructor() method returns the instance object (ie this) by default
class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo// false
//constructor()函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。

Instance of the class

  • Like ES5, unless the properties of an instance are explicitly defined in itself (that is, defined on the this object), they are all defined on the prototype (that is, defined on the 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.__proto__.hasOwnProperty('toString') // true

In the above code, x and y are the properties of the instance object point itself (because it is defined on the this variable), so the hasOwnProperty() method returns true, and toString() is the property of the prototype object (because it is defined on the Point class), So the hasOwnProperty() method returns false. These are consistent with the behavior of ES5.

  • All instances of the class share a prototype object.
var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__ === p2.__proto__
//true

__ proto __ is not a feature of the language itself, it is a private attribute added by major manufacturers during the specific implementation. Although this private attribute is currently provided in the JS engines of many modern browsers, it is still not recommended to use this attribute in production , To avoid dependence on the environment. In the production environment, we can use the Object.getPrototypeOf method to get the prototype of the instance object, and then add methods/attributes to the prototype.

note

  • Strict mode
  • There is no promotion
new Foo(); // ReferenceError
class Foo {}
  • name attribute
class Point {}
Point.name // "Point"
  • Generator method
class Foo {
  constructor(...args) {
    this.args = args;
  }
  * [Symbol.iterator]() {
    for (let arg of this.args) {
      yield arg;
    }
  }
}

for (let x of new Foo('hello', 'world')) {
  console.log(x);
}
  • the point of this

If there is this inside the method of a class, it points to an instance of the class by default. However, you must be very careful, once you use this method alone, it is likely to report an error.

class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

In the above code, this in the printName method refers to an instance of the Logger class by default. However, if this method is extracted and used alone, this will point to the environment in which the method is running (because the class is in strict mode, this actually points to undefined), which will cause the print method to not be found and an error will be reported.

Solution:

  • A relatively simple solution is to bind this in the construction method, so that the print method will not be found.
class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}
  • Use arrow functions.

Static methods and methods

The class is equivalent to the prototype of the instance, and all methods defined in the class will be inherited by the instance. If you add the static keyword before a method, it means that the method will not be inherited by the instance, but will be called directly through the class, which 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

Static properties refer to the properties of the Class itself, namely Class.propName, rather than the properties defined on the instance object (this).

/ 老写法
class Foo {
  // ...
}
Foo.prop = 1;

// 新写法
class Foo {
  static prop = 1;
}

Note that if the static method contains the this keyword, this 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

The static method of the parent class can be inherited by the child class. //Note that it is a subclass and not an instance

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

class Bar extends Foo {
}

Bar.classMethod() // 'hello'

Static methods can also be called from the super object.

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

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

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

New way of writing instance attributes: In addition to being defined on this in the constructor() method, instance attributes can also be defined at the top level of the class.

class IncreasingCounter {
  _count = 0;
  get value() {
    console.log('Getting the current value!');
    return this._count;
  }
  increment() {
    this._count++;
  }
}
//上面代码中,实例属性_count与取值函数value()和increment()方法,处于同一个层级。这时,不需要在实例属性前面加上this。

new.target attribute

new is the command to generate instance objects from the constructor. ES6 introduces a new.target attribute for the new command, which is generally used in the constructor and returns the constructor that the new command acts on. If the constructor is not called by the new command or Reflect.construct(), new.target will return undefined, so this property can be used to determine how the constructor is called.

It should be noted that when the subclass inherits the parent class, new.target will return the subclass.

class inheritance

The essence of ES5 inheritance is to create an instance object this of the subclass first, and then add the method of the parent class to this (Parent.apply(this)). The inheritance mechanism of ES6 is completely different. The essence is to first add the properties and methods of the instance object of the parent class to this (so the super method must be called first), and then use the constructor of the subclass to modify this.

In the constructor of the subclass, you can use the this keyword only after calling super, otherwise an error will be reported. This is because the construction of the subclass instance is based on the super class instance, and only the super method can call the super class instance.

1. Object.getPrototypeOf()

The Object.getPrototypeOf method can be used to get the parent class from the subclass.

2.super

The super keyword can be used as a function or as an object. In these two cases, its usage is completely different.

  • In the first case, when super is called as a function, it represents the constructor of the parent class. As a function, super() can only be used in the constructor of a subclass, and it will report an error when used in other places.
//ES6 要求,子类的构造函数必须执行一次super函数。
class A {}

class B extends A {
  constructor() {
    super();
  }
}

Note that although super represents the constructor of the parent class A, it returns an instance of the subclass B, that is, this inside super refers to an instance of B, so super() is equivalent to A.prototype.constructor.call here. (this).

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

In the above code, new.target points to the currently executing function. As you can see, when super() is executed, it points to the constructor of the subclass B, not the constructor of the parent class A. In other words, this inside super() points to B.

  • When super is an object

In the ordinary method, it points to the prototype object of the parent class; in the static method, it points to the parent class.

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

When calling the method of the parent class through super in the ordinary method of the subclass, the this inside the method points to the current instance of the subclass.

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

let b = new B();
b.m() // 2
  • When used in a static method, super will point to the parent class instead of the prototype object of the parent class.
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
//上面代码中,super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。

When calling the method of the parent class through super in the static method of the subclass, the this inside the method points 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();
    this.x = 2;
  }
  static m() {
    super.print();
  }
}

B.x = 3; //添加B的静态属性
B.m() // 3
//上面代码中,静态方法B.m里面,super.print指向父类的静态方法。这个方法里面的this指向的是B,而不是B的实例。

prototype property and _proto_ property

1. The __proto__ attribute of the subclass indicates the inheritance of the constructor and always points to the parent class.
2. The __proto__ attribute of the prototype attribute of the subclass indicates the inheritance of the method and 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

This result is because the inheritance of the class is implemented in accordance with the following pattern.

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

class A {
}

class B {
}

// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);

// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);

const b = new B();

Therefore, the above result is obtained.

understanding

These two inheritance chains can be understood like this: as an object, the prototype (__proto__ attribute) of the subclass (B) is the parent (A); as a constructor, the prototype object (prototype attribute) of the subclass (B) It is an instance of the prototype object (prototype attribute) of the parent class.
Insert picture description here
https://blog.csdn.net/jiaojsun/article/details/86773523Insert picture description here

(Finally: I hope you will teach you the prototype attribute and _proto_ attribute of the class)

to sum up:

How to implement class inheritance in ES6? : The es6 class implements inheritance through the extends keyword, and the subclass's constructor must execute the super function once. As a function call, super represents the constructor of the parent class, but this inside super refers to an instance of B. (equivalent to A.prototype.constructor.call(this).)

ES6 extends the compiled JavaScript code

function _possibleConstructorReturn (self, call) { 
		// ...
		return call && (typeof call === 'object' || typeof call === 'function') ? call : self; 
}

function _inherits (subClass, superClass) { 
    // ...
    //看到没有
		subClass.prototype = Object.create(superClass && superClass.prototype, { 
				constructor: { 
						value: subClass, 
						enumerable: false, 
						writable: true, 
						configurable: true 
				} 
		}); 
		if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}


var Parent = function Parent () {
		// 验证是否是 Parent 构造出来的 this
		_classCallCheck(this, Parent);
};

var Child = (function (_Parent) {
		_inherits(Child, _Parent);

		function Child () {
				_classCallCheck(this, Child);
		
				return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
		}

		return Child;
}(Parent));

The core is the _inherits function, you can see that it adopts the parasitic combination inheritance method , and at the same time proves the success of this method. But here is added an Object.setPrototypeOf (subClass, superClass), what is this used for?

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

The answer is to inherit the static method of the parent class. This is where the original inheritance method was neglected.

Follow up:

Reference:
ECMAScript 6 introductory
ES6 prototype attribute and __proto__ attribute of class-09

Guess you like

Origin blog.csdn.net/HZ___ZH/article/details/110948449