This is the second day of my participation in the August More Text Challenge

The presence of the class

Since we’ve spent a lot of time showing you how to use ES5 features to simulate class-like behavior, the code that implements inheritance is so long and confusing that the new class keyword introduced in ES6 has the ability to formally define classes. Although it appears to support formal object-oriented programming, the concepts of prototypes and constructors are still used behind the scenes. We usually affectionately call her Grammar sugar.

If you haven’t read about it, I recommend reading inheritance in JavaScript

If you have the basic concept of a class, you can jump to class inheritance by clicking on the right TAB

The class definition

Classes are defined in two ways: class declarations and class expressions

/ / the class declaration
class Person {}// Class expression
const Animal = class {};
Copy the code

Function expressions are similar to function expressions in that they cannot be referenced until evaluated, except that:

  • Function declarations can be promoted, but class definitions cannot
  • Functions are scoped by functions, but classes are scoped by blocks
console.log(FunctionExpression); // undefined
var FunctionExpression = function() {};
console.log(FunctionExpression); // function() {}
// If var is changed to let or const, an error will be reported. See JS variable promotion for details

console.log(FunctionDeclaration); // FunctionDeclaration() {}
function FunctionDeclaration() {}
console.log(FunctionDeclaration); // FunctionDeclaration() {}

console.log(ClassExpression); // undefined
var ClassExpression = class {};
console.log(ClassExpression); // class {}

console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined
class ClassDeclaration {}
console.log(ClassDeclaration); // class ClassDeclaration {}
Copy the code

Introduction of a class

The world of simulation classes

This may seem boring, but let’s take a look at an example. Before ES6, how did we generate instance objects? That’s right! Constructor.

function Person(name, actor) {
  this.name = name;
  this.actor = actor;
}

Person.prototype.tostring = function () {
  return `The ${this.name}.The ${this.actor}`;
};

const zhangsan = new Person('Joe'.'Outlaw maniac');;
Copy the code

This is how constructors generate instances in the previous chapter. So in ES6, with the introduction of class, let’s look at implementing the same code with class.

class Person {
  constructor (name, actor) {
    this.name = name;
    this.actor = actor;
  }

  tostring () {
    return `The ${this.name}.The ${this.actor}`; }}const zhangsan = new Person('Joe'.'Outlaw maniac');;
Copy the code

View the inner world of the class

Then let’s take a look at the internal structure of the Person class and what kind of inner world the instantiated Joe has.

console.dir(Person);
console.dir(zhangsan);
Copy the code

Analyze the inner world of class

The typeof Person is function, and the class itself points to the constructor. The prototype property is still attached to Person. We’ll see that the toString () method we defined earlier is also on the prototype of the class.

typeof Person; // "function"

Person.prototype.constructor === Person; // true

zhangsan.__proto__ === Person.prototype; // true
zhangsan.constructor === Person; // true

// This is actually calling the toString () method defined on the prototype of the class
zhangsan.tostring(); // "< span style =" max-width: 100%;
Copy the code

Come to the conclusion

Lu Xun: In fact, there is no road in this world. When more people walk, it becomes a road.

Classes are just syntactic sugar. Why are there classes? You can imagine all programmers as product managers and technical developers. They are madly coding their own encapsulated prototype methods, while lamenting why JavaScript does not have the concept of Java language classes. Programmers with the same project design philosophy would then participate in the open source JavaScript technology discussion, gradually taking into account all of the ideas and code contributions, resulting in the new concept class, which, yes, is also based on the pre-ES5 version, but is officially named.

The difference between

function Person(name, actor) {
  this.name = name;
  this.actor = actor;
}

Person.prototype.tostring = function () {
  return `The ${this.name}.The ${this.actor}`;
};

Object.keys(Person.prototype); // ["tostring"]

class Person {
  constructor (name, actor) {
    this.name = name;
    this.actor = actor;
  }

  tostring () {
    return `The ${this.name}.The ${this.actor}`; }}Object.keys(Person.prototype); / / []
Object.getOwnPropertyNames(Person.prototype) // ["constructor", "tostring"]
Copy the code

You can see that methods on the class prototype cannot be enumerated

The constructor method

The constructor() method is the default method of the class and is automatically called when instance objects are 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 Person {}/ / is equivalent to
class Person {
  constructor(){}}Copy the code

The constructor() method returns the instance object (that is, this) by default, although we could just as easily return another object

class Person {
  constructor() {
    return Object.create(null); }}new Person() instanceof Person; // false
Copy the code

Instances of the class

Instances of generated classes are written exactly as in ES5, using the new command. If you forget to add new and call Class as a function, an error will be reported.

class Person {
  constructor (name, actor) {
    this.name = name;
    this.actor = actor;
  }

  tostring () {
    return `The ${this.name}.The ${this.actor}`; }}const zhangsan = new Person('Joe'.'Outlaw maniac');
const tiezhu = new Person('iron column'.'Professional mage');

zhangsan.tostring(); // "< span style =" max-width: 100%;
zhangsan.hasOwnProperty('name'); // true
zhangsan.hasOwnProperty('actor'); // true
zhangsan.hasOwnProperty('tostring'); // false
zhangsan.__proto__.hasOwnProperty('tostring'); // true

// Zhangsan and TieZhu are both instances of Person, and their prototype is Person.prototype
// So we can add methods to classes via __proto__, but this is not recommended as it affects all instances
zhangsan.__proto__ === tiezhu.__proto__; // true
Copy the code

Other features

Getters and setters are supported in classes

As in ES5, you can use the get and set keywords inside a class to set the store and value functions for a property and intercept the access behavior of that property.

class Person {
  constructor(){}get name() {
    return 'Joe';
  }

  set name(value) {
    console.log('setter: '+ value); }}const zhangsan = new Person();

zhangsan.name = 'Joe';
/ / setter: zhang SAN

zhangsan.name;
// select * from *;
Copy the code

A static method

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 Person {
  static sayHello() {
    return 'hello';
  }
}

Person.sayHello(); // 'hello'

var zhangsan = new Person();
zhangsan.sayHello();
// TypeError: foo.classMethod is not a function
Copy the code

If a static method contains the this keyword, this refers to the current class, not the instance, since the instance cannot inherit from the static method.

Static attributes

ES6 explicitly states that there are only static methods inside a Class and no static properties, but we can define them on the prototype of the Class

class Person {
}

Person.name = 'Joe';
Person.name / / zhang SAN
Copy the code

New writing of instance attributes (not recommended)

Normally we write instance attributes above this in the constructor() method

class Person {
  constructor(name, actor) {
      this.name = name;
      this.actor = actor; }}Copy the code

But there’s another way to write it that’s at the top of the class, but I recommend that

class Person {
  name = 'Joe';
  actor = 'Outlaw maniac'
  constructor(){}}Copy the code

The class inheritance

Class instantiation

Before we look at inheritance of a class implementation, let’s look at the process of instantiating a class

  1. First, a new object is created in memory
  2. And then the internal of this new object__proto__Property assigned to the constructorprototypeattribute
  3. This inside the constructor is assigned to the new object (this refers to the new object).
  4. Execute code inside constructor (add attributes to new object)
  5. If the constructor returns a non-empty object, that object is returned; Otherwise, the default return is the newly created object

Inheritance based

It’s a new syntax, but it’s just syntactic sugar, and it’s actually a chain of prototypes behind it

Backward compatibility is maintained and any object with construct and stereotypes can be inherited

function Person(name, actor) {
  this.name = name;
  this.actor = actor;
}

// Inherit the constructor
class ActivePerson extends Person {}
// -------------------------------------
class Person {
  constructor(name, actor) {
      this.name = name;
      this.actor = actor; }}/ / a derived class
class ActivePerson extends Person {}
Copy the code

The super keyword

This keyword can only be used in derived classes and can be used as either a function or an object

  • When called as a function, represents the parent class’s constructor, and ES6 requires that the subclass’s constructor be executed oncesuperFunction, and can only be used in the constructor of a subclass
class A {}

class B extends A {
  constructor() {
    super(a);// If super() is not written here, an error will be reported}}Copy the code

Although here is representative of A constructor, but returns the instance of B, that is super () inside this point to an instance of B, so super () is equivalent to Amy polumbo rototype. Call (this) constructor.

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

new A() // A
new B() // B
Copy the code

New.target refers to the function currently being executed, so it points to the constructor of B when super() is executed

class A {}

class B extends A {
  m() {
    super(a);/ / an error}}Copy the code

The above shows that when super() is called as a function, it can only be used in the constructor of a subclass

  • superWhen used as an object, if in a normal method, a prototype object pointing to the parent class; If it’s a static method, it points 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

Here super.p() refers to a.prototype in normal methods. So we can access the methods on the parent prototype, but we can’t access the properties and methods on the parent instance

class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super(a);console.log(super.x) / / 2}}Copy the code

Use super in normal methods:

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

Here we have to explain: who does this refer to? That’s right, B!

So when we call the b.m() method, super.print() is equivalent to a.prototype.print (), but this inside print() points to an instance of subclass B.

Using super in static methods:

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

const 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.

Of the classprototypeProperties and__proto__Attributes (emphasis)

As we explained in the previous chapter, each object has a __proto__ attribute that points to the prototype attribute of the corresponding constructor, and class is the syntactic sugar of the constructor that has both the Prototype attribute and the __proto__ attribute, so there are two inheritance chains

① : The __proto__ attribute of a subclass, indicating the inheritance of the constructor, always points to the parent class

The prototype attribute is __proto__, which indicates the method inheritance and always points to the prototype attribute of the parent class

Maybe this is a little broad, so let’s go straight to the demo

class A {}class B extends A {
}

B.__proto__ === A; // true
B.prototype.__proto__ === A.prototype; // true
Copy the code

Why is this so? Let’s examine how class inheritance is implemented.

class A {}class B {}// Object. SetPrototypeOf implementation
Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

// Instance B inherits instance A
Object.setPrototypeOf(B.prototype, A.prototype);
/ / is equivalent to
B.prototype.__proto__ = A.prototype;

// B inherits A's static attributes
Object.setPrototypeOf(B, A);
/ / is equivalent to
B.__proto__ = A;

const b = new B();
Copy the code

As an object, the prototype (__proto__ attribute) of subclass B is superclass A;

As a constructor, the prototype object of subclass B (the Prototype property) is an instance of the prototype object of the parent class

B.prototype = Object.create(A.prototype);
/ / is equivalent to
B.prototype.__proto__ = A.prototype;
Copy the code

The instance__proto__attribute

The __proto__.__proto__ attribute of a subclass instance points to the __proto__ attribute of the parent class instance.

That is, the prototype of the subclass is the prototype of the parent class

class Person {
  constructor(name, actor) {
    this.name = name;
    this.actor = actor; }}class ActivePerson extends Person {
  constructor(name, actor, hobby) {
    super(a);this.hobby = hobby; }}const person1 = new Person('Joe'.'Outlaw maniac');
const activePerson1 = new ActivePerson('Joe'.'Outlaw maniac'.'Perfect crime');

person1.__proto__ === activePerson1.__proto__; // false
activePerson1.__proto__.__proto__ === person1.__proto__; // true
Copy the code

Allows native constructors to define subclasses

Let’s look at an inheritanceArrayAn example of

class MyArray extends Array {
  constructor(. args) {
    super(...args);
  }
}

const arr = new MyArray();
arr[0] = 12;
arr.length; / / 1

arr.length = 0;
arr[0]; // undefined
Copy the code

As a result, we can easily extend or rewrite the methods of the prototype here to encapsulate the data structures we need.

The customError

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); }}const myerror = new MyError('ll');
myerror.message // "ll"
myerror instanceof Error // true
myerror.name // "MyError"
myerror.stack
Copy the code

Analysis of theclassinheritance

Let’s start with parasitic combinatorial inheritance

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

function Student(props) {
    this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '! ');
}

function PrimaryStudent(props) {
    Student.call(this, props);
    this.grade = props.grade || 1;
}

// Implement the prototype inheritance chain:
inherits(PrimaryStudent, Student);

// Bind other methods to the PrimaryStudent prototype:
PrimaryStudent.prototype.getGrade = function () {
    return this.grade;
};
Copy the code

When we call the inherits function, we make a backup of the parent class’s prototype, assign the instance of the parent class’s prototype to the subclass’s prototype, and then rewrite the constructor of the subclass to refer back to ourselves to achieve inheritance on the prototype method, and use the constructor inheritance to achieve inheritance of the parent class attributes.

Take a look atclassInheritance of implementation

class Student {
  constructor(props) {
    this.name = props.name || 'Unnamed';
  }

  hello () {
    alert('Hello, ' + this.name + '! '); }}class PrimaryStudent extends Student {
  constructor(props) {
    super(props);
    this.grade = props.grade || 1;
  }

  getGrade () {
    return this.grade; }}Copy the code

Use the Babel online conversion tool to convert es6 syntax to ES5 syntax

Then look at the results of the transformation, taking out only the important parts:

"use strict";

function _inherits(subClass, superClass) { if (typeofsuperClass ! = ="function"&& superClass ! = =null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true.configurable: true}});if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this.arguments); } return _possibleConstructorReturn(this, result); }; }

var Student = /*#__PURE__*/function () {
  function Student(props) {
    _classCallCheck(this, Student);

    this.name = props.name || 'Unnamed';
  }

  _createClass(Student, [{
    key: "hello".value: function hello() {
      alert('Hello, ' + this.name + '! '); }}]);returnStudent; } ();var PrimaryStudent = /*#__PURE__*/function (_Student) {
  _inherits(PrimaryStudent, _Student);

  var _super = _createSuper(PrimaryStudent);

  function PrimaryStudent(props) {
    var _this;

    _classCallCheck(this, PrimaryStudent);

    _this = _super.call(this, props);
    _this.grade = props.grade || 1;
    return _this;
  }

  _createClass(PrimaryStudent, [{
    key: "getGrade".value: function getGrade() {
      return this.grade; }}]);return PrimaryStudent;
}(Student);
Copy the code

We are looking at the _inherits(PrimaryStudent, _Student) method and the _super.call(this, props) method. But there is an extra _setPrototypeOf operation at the end, and then look at the _setPrototypeOf method and see that it points o.__proto__ = p, which refers the implicit prototype of the subclass to the parent class, so why do that? That’s right! To inherit static methods from the parent class, and remember, when we do parasitic combinatorial inheritance, we can inherit properties from the parent class and methods from the prototype, but we don’t talk about static methods, although I don’t use static methods very much, It should mean a method defined directly on a constructor that can be used directly on a constructor but not on an instance.

The _super.call(this, props) method is super. apply(this, arguments). This is the same as es5.

So, we can use object.getPrototypeof () to get the parent class from a subclass; You can use this method to determine whether a class inherits from another class.

Object.getPrototypeOf(PrimaryStudent) === Student; // true
Copy the code

The resources

  • JavaScript Advanced Programming
  • ECMAScript 6 Introduction to class