Definition and inheritance of JavaScript ES6 classes

Article directory

  • 1. Define classes in class mode
    • 1. Understand class definition class
    • 2. Similarities and differences between classes and constructors
    • 3. Class constructor
    • 4. Instance methods of classes
    • 5. Accessor methods of classes
    • 6. Static methods of classes
  • 2. Inheritance
    • 1.extends implements inheritance
    • 2.super keyword
    • 3. Inherit built-in classes
    • 4. Class mixing into mixin
  • 3. ES6 to ES5
    • 1.class conversion
    • 2.extends conversion
  • 4. Polymorphism

1. Define classes in class mode

1. Understand class and define classes

We will find that creating a class according to the previous constructor form is not only too similar to writing an ordinary function, but also the code is not easy to understand.

  • In the new standard of ES6 (ECMAScript2015), the class keyword is used to directly define classes;
  • But classes are still essentially syntactic sugar for the constructors and prototype chains mentioned earlier;
  • Therefore, learning the previous constructors and prototype chains will help us understand the concept and inheritance relationship of classes;

So, how to use class to define a class?

  • There are two ways to declare a class: class declaration and class expression;
//Method 1 Class declaration
class Person {<!-- -->

}

// Method 2 Class expression
var Student = class {<!-- -->

}

2. Similarities and differences between classes and constructors

Let’s examine some features of classes:

  • You will find that it has the same characteristics as our constructor;
// function definition class
function Person1(name, age) {<!-- -->
    this.name = name
    this.age = age
}

Person1.prototype.running = function() {<!-- -->}
Person1.prototype.eating = function() {<!-- -->}

var p1 = new Person1("why", 18)
console.log(p1.__proto__ === Person1.prototype)
console.log(Person1.prototype.constructor)
console.log(typeof Person1) // function

// Difference: Called as a normal function
Person1("abc", 100)

// class definition class
class Person2 {<!-- -->
    constructor(name, age) {<!-- -->
        this.name = name
        this.age = age
    }

    running() {<!-- -->}
    eating() {<!-- -->}
}

var p2 = new Person2("kobe", 30)
console.log(p2.__proto__ === Person2.prototype)
console.log(Person2.prototype.constructor)
console.log(typeof Person2)

// Difference: The class defined by class cannot be called as an ordinary function.
Person2("cba", 0)

3. Class constructor

If we want to pass some parameters to the class when creating an object, what should we do at this time?

  • Each class can have its own constructor (method), and the name of this method is fixed constructor;
  • When we operate a class through the new operator, the constructor of this class will be called;
  • Each class can only have one constructor. If it contains multiple constructors, an exception will be thrown;

When we operate a class through the new keyword, we will call this constructor function and perform the following operations:

  1. Create a new object (empty object) in memory;

  2. The [[prototype]] attribute inside this object will be assigned the value of the prototype attribute of the class;

  3. This inside the constructor will point to the new object created;

  4. Execute the internal code of the constructor (function body code);

  5. If the constructor does not return a non-null object, the new object created is returned;

class Person {<!-- -->
    constructor(name, age) {<!-- -->
        this.name = name
        this.age = age
    }
}

4. Instance methods of classes

The properties we defined above are placed directly on this, which means that they are placed in the new object created:

  • We said before that for instance methods, we hope to place them on the prototype so that they can be shared by multiple instances;
  • At this time we can define it directly in the class;
class Person {<!-- -->
    constructor(name, age) {<!-- -->
        this.name = name
        this.age = age
    }

    run(){<!-- -->
        console.log(this.name + "running~")
    }
}

5. Accessor methods of classes

When we talked about object property descriptors before, we mentioned that objects can add setter and getter functions, so classes can also add:

class Student {<!-- -->
    constructor(name, age) {<!-- -->
        this._name = name
        this._age = age
    }

    set name(name) {<!-- -->
        this._name = name
    }

    get name() {<!-- -->
        return this._name
    }
}
var s = new Student("abc", 123)
console.log(s.name)

Extended Review: Accessor Methods of Objects

 // for objects
// Method 1: Descriptor
// var obj = {<!-- -->
// _name: "why"
// }
// Object.defineProperty(obj, "name", {<!-- -->
// configurable: true,
// enumerable: true,
// set: function() {<!-- -->
// },
// get: function() {<!-- -->
// }
// })

// Method 2: Define the accessor directly on the object
// Monitor when _name is accessed and set a new value
var obj = {<!-- -->
    _name: "why",
    // setter method
    set name(value) {<!-- -->
        this._name = value
    },
    // getter method
    get name() {<!-- -->
        return this._name
    }
}

obj.name = "kobe"
console.log(obj.name)

Accessor application scenarios

// 2. Application scenarios of accessor
class Rectangle {<!-- -->
    constructor(x, y, width, height) {<!-- -->
        this.x = x
        this.y = y
        this.width = width
        this.height = height
    }

    get position() {<!-- -->
        return {<!-- --> x: this.x, y: this.y }
    }

    get size() {<!-- -->
        return {<!-- --> width: this.width, height: this.height }
    }
}

var rect1 = new Rectangle(10, 20, 100, 200)
console.log(rect1.position)
console.log(rect1.size)

6. Static methods of classes

Static methods are usually used to define methods that are executed directly using the class. There is no need for an instance of the class. Use the static keyword to define:

// function Person() {}
// //Instance methods
// Person.prototype.running = function() {}
// // Class methods
// Person.randomPerson = function() {}

// var p1 = new Person()
//p1.running()
// Person.randomPerson()

//Class defined by class
var names = ["abc", "cba", "nba", "mba"]
class Person {<!-- -->
    constructor(name, age) {<!-- -->
        this.name = name
        this.age = age
    }

    //Instance method
    running() {<!-- -->
        console.log(this.name + "running~")
    }
    eating() {<!-- -->}

    // Class method (static method)
    static randomPerson() {<!-- -->
        console.log(this)
        var randomName = names[Math.floor(Math.random() * names.length)]
        return new this(randomName, Math.floor(Math.random() * 100))
    }
}

var p1 = new Person()
p1.running()
p1.eating()
var randomPerson = Person.randomPerson()
console.log(randomPerson)

2. Inheritance

1.extends implements inheritance

We have spent a lot of time discussing the solution to implement inheritance in ES5. Although we finally achieved a relatively satisfactory inheritance mechanism, the process is still very cumbersome.

In ES6, the extends keyword is newly added, which can easily help us implement inheritance:

class Person {<!-- -->}
class Student extends Person {<!-- -->}

Sample code

//Define parent class
class Person {<!-- -->
    constructor(name, age) {<!-- -->
        this.name = name
        this.age = age
    }

    running() {<!-- -->
        console.log("running~")
    }
    eating() {<!-- -->
        console.log("eating~")
    }
}

class Student extends Person {<!-- -->
    constructor(name, age, sno, score) {<!-- -->
        // this.name = name
        // this.age = age
        super(name, age)
        this.sno = sno
        this.score = score
    }

    // running() {<!-- -->
    // console.log("running~")
    // }
    // eating() {<!-- -->
    // console.log("eating~")
    // }

    studying() {<!-- -->
        console.log("studying~")
    }
}

var stu1 = new Student("why", 18, 111, 100)
stu1.running()
stu1.eating()
stu1.studying()

class Teacher extends Person {<!-- -->
    constructor(name, age, title) {<!-- -->
        // this.name = name
        // this.age = age
        super(name, age)
        this.title = title
    }

    // running() {<!-- -->
    // console.log("running~")
    // }
    // eating() {<!-- -->
    // console.log("eating~")
    // }

    teaching() {<!-- -->
        console.log("teaching~")
    }
}

2.super keyword

We will find that in the above code I used a super keyword. This super keyword can be used in different ways:

  • Note: Before using this in the constructor of a child (derived) class or returning the default object, you must call the constructor of the parent class through super first!
  • There are three places where super can be used: constructor, instance method, and static method of subclasses;
class Animal {<!-- -->
    running() {<!-- -->
        console.log("running")
    }
    eating() {<!-- -->
        console.log("eating")
    }

    static sleep() {<!-- -->
        console.log("static animal sleep")
    }
}

class Dog extends Animal {<!-- -->
    // If the subclass is not satisfied with the method implementation of the parent class (inherited method)
    // Reimplementation is called overriding (rewriting of parent class methods)
    running() {<!-- -->
        console.log("dog has four legs")
        // Call the parent class method
        super.running()
        // console.log("running~")
        // console.log("dog running with four legs~")
    }

    static sleep() {<!-- -->
        console.log("lying")
        super.sleep()
    }
}

var dog = new Dog()
dog.running()
dog.eating()

Dog.sleep()

3. Inherit built-in classes

We can also have our class inherit from a built-in class, such as Array:

 // 1. Create a new class, inherit from Array and extend it
class HYArray extends Array {<!-- -->
    get lastItem() {<!-- -->
        return this[this.length - 1]
    }

    get firstItem() {<!-- -->
        return this[0]
    }
}

var arr = new HYArray(10, 20, 30)
console.log(arr)
console.log(arr.length)
console.log(arr[0])
console.log(arr.lastItem)
console.log(arr.firstItem)

// 2. Directly extend Array
Array.prototype.lastItem = function() {<!-- -->
    return this[this.length - 1]
}

var arr = new Array(10, 20, 30)
console.log(arr.__proto__ === Array.prototype)
console.log(arr.lastItem())

// Function apply/call/bind method -> Function.prototype

4. Class mixing into mixin

JavaScript classes only support single inheritance: that is, there can only be one parent class

So when we need to add more similar functions to a class during development, how should we do it?

At this time we can use Mixin (mixin);

//JavaScript only supports single inheritance (does not support multiple inheritance)
function mixinAnimal(BaseClass) {<!-- -->
    return class extends BaseClass {<!-- -->
        running() {<!-- -->
            console.log("running~")
        }
    }
}

function mixinRunner(BaseClass) {<!-- -->
    return class extends BaseClass {<!-- -->
        flying() {<!-- -->
            console.log("flying~")
        }
    }
}

class Bird {<!-- -->
    eating() {<!-- -->
        console.log("eating~")
    }
}

// var NewBird = mixinRunner(mixinAnimal(Bird))
class NewBird extends mixinRunner(mixinAnimal(Bird)) {<!-- -->
}
var bird = new NewBird()
bird.flying()
bird.running()
bird.eating()

3. ES6 to ES5

1.class conversion

ES6 code

class Person {<!-- -->
    constructor(name, age) {<!-- -->
        this.name = name
        this.age = age
    }

    running() {<!-- -->}
    eating() {<!-- -->}

    static randomPerson() {<!-- -->}
}

var p1 = new Person()

ES5 code

"use strict";

function _classCallCheck(instance, Constructor) {<!-- -->
  if (!(instance instanceof Constructor)) {<!-- -->
    throw new TypeError("Cannot call a class as a function");
  }
}

function _defineProperties(target, props) {<!-- -->
  for (var i = 0; i < props.length; i + + ) {<!-- -->
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {<!-- -->
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, "prototype", {<!-- --> writable: false });
  return Constructor;
}

//Pure function: The same input must produce the same output and will not produce side effects
var Person = /*#__PURE__*/ (function () {<!-- -->
  debugger

  function Person(name, age) {<!-- -->
    _classCallCheck(this, Person);

    this.name = name;
    this.age = age;
  }

  _createClass(
    Person,
    [
      {<!-- -->
        key: "running",
        value: function running() {<!-- -->}
      },
      {<!-- -->
        key: "eating",
        value: function eating() {<!-- -->}
      }
    ],
    [
      {<!-- -->
        key: "randomPerson",
        value: function randomPerson() {<!-- -->}
      }
    ]
  );

  return Person;
})();

var p1 = new Person("why", 18)

2.extends conversion

ES6 code

class Person {<!-- -->
    constructor(name, age) {<!-- -->
        this.name = name
        this.age = age
    }

    running() {<!-- -->}
    eating() {<!-- -->}

    static randomPerson() {<!-- -->}
}

class Student extends Person {<!-- -->
    constructor(name, age, sno, score) {<!-- -->
        super(name, age)
        this.sno = sno
        this.score = score
    }

    studying() {<!-- -->}
    static randomStudent() {<!-- -->}
}

var stu = newStudent()

ES5 code

"use strict";

function _typeof(obj) {<!-- -->
  "@babel/helpers - typeof";
  return (
    (_typeof =
      "function" == typeof Symbol & amp; & amp; "symbol" == typeof Symbol.iterator
        ? function (obj) {<!-- -->
            return typeof obj;
          }
        : function (obj) {<!-- -->
            return obj & amp; & amp;
              "function" == typeof Symbol & amp; & amp;
              obj.constructor === Symbol & amp; & amp;
              obj !== Symbol.prototype
              ? "symbol"
              : typeof obj;
          }),
    _typeof(obj)
  );
}

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

function _setPrototypeOf(o, p) {<!-- -->
  _setPrototypeOf = Object.setPrototypeOf
    ? Object.setPrototypeOf.bind()
    : 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);
  };
}

function _possibleConstructorReturn(self, call) {<!-- -->
  if (call & amp; & amp; (_typeof(call) === "object" || typeof call === "function")) {<!-- -->
    return call;
  } else if (call !== void 0) {<!-- -->
    throw new TypeError(
      "Derived constructors may only return object or undefined"
    );
  }
  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {<!-- -->
  if (self === void 0) {<!-- -->
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  }
  return self;
}

function _isNativeReflectConstruct() {<!-- -->
  if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy === "function") return true;
  try {<!-- -->
    Boolean.prototype.valueOf.call(
      Reflect.construct(Boolean, [], function () {<!-- -->})
    );
    return true;
  } catch (e) {<!-- -->
    return false;
  }
}

function _getPrototypeOf(o) {<!-- -->
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf.bind()
    : function _getPrototypeOf(o) {<!-- -->
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

function _classCallCheck(instance, Constructor) {<!-- -->
  if (!(instance instanceof Constructor)) {<!-- -->
    throw new TypeError("Cannot call a class as a function");
  }
}

function _defineProperties(target, props) {<!-- -->
  for (var i = 0; i < props.length; i + + ) {<!-- -->
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {<!-- -->
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, "prototype", {<!-- --> writable: false });
  return Constructor;
}

var Person = /*#__PURE__*/ (function () {<!-- -->
  function Person(name, age) {<!-- -->
    _classCallCheck(this, Person);
    this.name = name;
    this.age = age;
  }
  _createClass(
    Person,
    [
      {<!-- -->
        key: "running",
        value: function running() {<!-- -->}
      },
      {<!-- -->
        key: "eating",
        value: function eating() {<!-- -->}
      }
    ],
    [
      {<!-- -->
        key: "randomPerson",
        value: function randomPerson() {<!-- -->}
      }
    ]
  );
  return Person;
})();

function inherit(SubType, SuperType) {<!-- -->
  SubType.prototype = Object.create(SuperType.prototype)
  SubType.prototype.constructor = SubType
}

var Student = /*#__PURE__*/ (function (_Person) {<!-- -->
  _inherits(Student, _Person);

  var _super = _createSuper(Student);

  function Student(name, age, sno, score) {<!-- -->
    var _this;

    _classCallCheck(this, Student);

    _this = _super.call(this, name, age);
    _this.sno = sno;
    _this.score = score;
    return _this;
  }

  _createClass(
    Student,
    [
      {<!-- -->
        key: "studying",
        value: function studying() {<!-- -->}
      }
    ],
    [
      {<!-- -->
        key: "randomStudent",
        value: function randomStudent() {<!-- -->}
      }
    ]
  );

  return Student;
})(Person);

var stu = new Student("why", 18, 111, 100);

4. Polymorphism

The three major characteristics of object-oriented: encapsulation, inheritance, and polymorphism.

Wikipedia’s definition of polymorphism: Polymorphism (English: polymorphism) refers to providing a unified interface for entities of different data types, or using a single symbol to represent multiple entities. different types.

Very abstract, personal summary: different data types perform the same operation and show different behaviors, which is the embodiment of polymorphism.

So from the above definition, JavaScript must have polymorphism.

//Performance of polymorphism: JS is full of polymorphism
function sum(a1, a2) {<!-- -->
    return a1 + a2
}

sum(20, 30)
sum("abc", "cba")

//polymorphic expression
var foo = 123
foo = "Hello World"
console.log(foo.split())
foo = {<!-- -->
    running: function() {<!-- -->}
}
foo.running()
foo = []
console.log(foo.length)