introduce

This article, the eleventh in a series of advanced JavaScript insights, continues with the tenth, looking at classes in the ES6 specification

The body of the

1. Use class to define classes

Defining a class in the same way as in Article 10 is not only not very different from normal functions, but the code is not easy to understand.

  • In the ES6 specification, directly usedclassKeyword to define the class.
  • But this is just syntactic sugar, and ultimately turns into the traditional way of defining a class

So, how do you define a class using class? There are two approaches: class declarations and class expressions

/ / the class declaration
class Person {}// Expression declarations are also possible, but are not recommended for development
var Student = class {}Copy the code

2. Class features

By examining the properties of the class, we can see that it shares some of the properties of the constructor

class Person {}

var p = new Person()

console.log(Person) // [class Person]
console.log(Person.prototype) / / {}
console.log(Person.prototype.constructor) // [class Person]
console.log(p.__proto__ === Person.prototype) // true
console.log(typeof Person) // function
Copy the code

2.1 Class constructors

Passing arguments to a class when creating an instance from its constructor. A class can have only one constructor (and therefore cannot override constructors as Java does).

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

var p = new Person('alex'.18)

console.log(p) // Person { name: 'alex', age: 18 }
Copy the code

To call the constructor, do the same thing as before:

  • Create an empty object
  • Class internalthisThat points to this empty objectthis
  • The class ofprototypeValue assigned to the empty object[[prototype]] (__proto__)attribute
  • Execute the constructor body
  • Returns the newly created object

2.2 Instance methods of the class

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  // RUNNING is an example method
  running() {
    console.log(`The ${this.name} is running`)}}var p = new Person('alex'.18)
p.running()
Copy the code
/ / is equivalent to
Person.prototype.running = function() {
    / /...
}
Copy the code

Print Object.getowndescripors (person.prototype) and get

{
  constructor: {
    value: [class Person].writable: true.enumerable: false.configurable: true
  },
  running: {
    value: [Function: running],
    writable: true.enumerable: false.configurable: true}}Copy the code

Accessor methods for class 2.3

var obj = {
  _name: 'obj'.get name() {
    return this._name
  },
  set name(val) {
    this._name = val
  },
}
Copy the code

Before ES6, we could define accessor methods for constructors. In class, we could define accessor methods like this:

class Person {
  constructor(name) {
    this._name = name
  }
  // Class accessor method
  get name() {
    return this._name
  }
  set name(val) {
    this._name = val
  }
}

const p = new Person('alex')
console.log(Object.getOwnPropertyDescriptors(p))

/* { _name: { value: 'alex', writable: true, enumerable: true, configurable: true } } */
Copy the code

Getter and setter accessor functions are used to intercept reads and writes

2.4 Static methods of class

Static method: a method in a class is static

class Person {
  constructor(name) {
    this._name = name
  }
  static greeting() {
    console.log(` Person class say hello `)}}const p = new Person('alex')
Person.greeting() / / success
p.greeting() // Error, no such method
Copy the code

For example, we could write a static method that creates a Person instance

class Person {
  constructor(name) {
    this._name = name
  }
  static greeting() {
    console.log(` Person class say hello `)}static createPerson(. params) {
    return newPerson(... params) } }Copy the code

Thus, there are two ways to create a Person instance, one using new and one calling the class’s static methods

const p = new Person('alex')
const p2 = Person.createPerson('john')
Copy the code

3. Implement inheritance

Instead of using ES5’s custom utility method to implement inheritance, ES6 uses extends to implement inheritance directly, but the effect is the same

class Person {
  constructor(name) {
    this.name = name
  }
  running() {
    console.log(`The ${this.name} is running`)}}The extends keyword extends from the parent class
class Student extends Person {
  constructor(name, sno) {
    // super calls the parent constructor
    // Pass in custom parameters
    super(name)
    this.sno = sno
  }
  studying() {
    console.log(`The ${this.name} is studying, and his sno is The ${this.sno}`)}}const s1 = new Student('alex'.1033)
s1.running() // alex is running
s1.studying() // alex is studying, and his sno is 1033
Copy the code

3.1 Super keyword

We found that super was used when we inherited a class above. Here are some things to note:

  • Used in the constructor of a child (derived) classthisOr return the default object,Must bethroughsuperCall the parent class’s constructor
  • superWhere: subclass constructors, instance methods, static methods

There are two main uses of super:

// Call the constructor of the parent class
super([arguments])
// Call a static method on the parent class
super.FunctionOnParent([arguments])
Copy the code

4. Method rewrite

// In real development, you might encounter a method of the parent class that implements some functionality
// Subclasses still need this method, but add their own logic
// So you can use method overrides
class Person {
  constructor(name) {
    this.name = name
  }
  running() {
    / /... Some logic
    console.log(`The ${this.name} is running`)}}class Student extends Person {
  constructor(name, sno) {
    super(name)
    this.sno = sno
  }
  // Subclasses and superclasses have methods of the same name, which are overrides of that method
  running() {
    // Call the method of the same name as the parent class to execute the logic
    super.running()
    console.log(`student The ${this.name} is running`)}}const s1 = new Student('alex'.1033)
s1.running() // Execute the running method logic for Person and Student
Copy the code

5. Class syntax sugar to ES5 code

Since many users have different browser versions, the old version is not compatible with the current new version of the syntax, so in order to accommodate the experience of most users, we usually use some tools (such as Babel) when writing the new syntax code, so that the old version of the browser can recognize

/ / ES6 code
class Person {
  constructor(name) {
    this.name = name
  }
  running() {
    console.log(`The ${this.name} is running`)}static staticMethod() {
    console.log(`Person static method`)}}// Babel converts to ES5 code 👇
function _classCallCheck(instance, Constructor) {
  if(! (instanceinstanceof 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)
  return Constructor
}

var Person = /*#__PURE__*/ (function() {
  function Person(name) {
    _classCallCheck(this, Person)

    this.name = name
  }

  _createClass(
    Person,
    [
      {
        key: 'running'.value: function running() {
          console.log(' '.concat(this.name, ' is running')},},], [{key: 'staticMethod'.value: function staticMethod() {
          console.log('Person static method'},},])return Person
})()
Copy the code

5.1 Parsing Code

First, the Person class is converted to a function, and the internal Person function checks the call. If the Person class is called as a function, TypeError is reported.

var Person = /*#__PURE__*/ (function() {
  function Person(name) {
    _classCallCheck(this, Person)

    this.name = name
  }

  _createClass(
    Person,
    [
      {
        key: 'running'.value: function running() {
          console.log(' '.concat(this.name, ' is running')},},], [{key: 'staticMethod'.value: function staticMethod() {
          console.log('Person static method'},},])return Person
})()
Copy the code

The core code is in the _createClass and _defineProperties functions

  • _createClassReceives three parameters,
    • The first argument is the target object to mount, which in the code isPerson.
    • The second argument takes an array of all instance methods defined, each of which is an object,keyIs the method name,valueIs the corresponding function
    • The third argument takes an array of all static methods defined, storing the same values as the second argument
    • This function validates instance methods and static methods and calls_definePropertiesIf it is an instance method, it is passedPerson.prototypeAnd an array of instance methods, if staticPersonAnd static method array
  • _definePropertiesReceives two parameters,
    • The first parameter is the target to which the properties need to be mounted
    • The second parameter is the array of properties to mount
    • For property array traversal, passObject.defineProperty()To mount properties to the target object one by one

In simple terms, mount a user-defined static method in the constructor and a user-defined instance method on the prototype of the constructor.

5.2 /*#__pure__*/

This is a flag that means to mark the function as a pure function. If you encounter a pure function flag when webPack is compressing and optimizing your code, you can tree-shaking the function.

The tree – shaking? If the function is not used during dependency analysis, all the code for the function is removed from the code tree, effectively reducing the size of the code

6. ES6 to ES5 inheritance code interpretation

/ / ES6 code
class Person {}
class Student extends Person {}
Copy the code
// Babel becomes ES5 code
'use strict'

function _typeof(obj) {
  '@babel/helpers - typeof'
  if (typeof Symbol= = ='function' && typeof Symbol.iterator === 'symbol') {
    _typeof = function _typeof(obj) {
      return typeof obj
    }
  } else {
    _typeof = function _typeof(obj) {
      return obj &&
        typeof Symbol= = ='function' &&
        obj.constructor === Symbol&& obj ! = =Symbol.prototype
        ? 'symbol'
        : typeof obj
    }
  }
  return _typeof(obj)
}

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

function _possibleConstructorReturn(self, call) {
  if (call && (_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
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o)
      }
  return _getPrototypeOf(o)
}

function _classCallCheck(instance, Constructor) {
  if(! (instanceinstanceof Constructor)) {
    throw new TypeError('Cannot call a class as a function')}}var Person = function Person() {
  _classCallCheck(this, Person)
}

var Student = /*#__PURE__*/ (function(_Person) {
  _inherits(Student, _Person)

  var _super = _createSuper(Student)

  function Student() {
    _classCallCheck(this, Student)

    return _super.apply(this.arguments)}return Student
})(Person)
Copy the code

6.1 Parsing Code

Start with statements

// Declare a Person constructor. If the constructor is called directly, an error is reported
var Person = function Person() {
  _classCallCheck(this, Person)
}

// Declare a subclass, which is the core method for implementing inheritance
var Student = /*#__PURE__*/ (function(_Person) {
  // the _inherits() function is used to implement inheritance
  _inherits(Student, _Person)

  var _super = _createSuper(Student)

  function Student() {
    _classCallCheck(this, Student)

    return _super.apply(this.arguments)}return Student
})(Person)
Copy the code

_inheritsfunction

function _inherits(subClass, superClass) {
  // do the boundary judgment
  if (typeofsuperClass ! = ='function'&& superClass ! = =null) {
    throw new TypeError('Super expression must either be null or a function')}// Combine parasitic inheritance
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true.configurable: true}})// _setPrototypeOf function, modify subClass prototype to superClass
  // Student.__proto__ = Person
  // The purpose of this step is static method inheritance
  if (superClass) _setPrototypeOf(subClass, superClass)
}

function _setPrototypeOf(o, p) {
  // Call this method directly if the object. setPrototypeOf method is used
  // If not implemented manually, change the proto. Due to some historical issues, directly changing __proto__ will not only cause compatibility problems, but also have serious performance defects, so do not change __proto__ if you can use setPrototypeOf directly
  _setPrototypeOf =
    Object.setPrototypeOf ||
    function _setPrototypeOf(o, p) {
      o.__proto__ = p
      return o
    }
  return _setPrototypeOf(o, p)
}
Copy the code

_createSuperfunction

This function returns a function for _super.apply(this, arguments) below. Why not call it directly? This function returns the createSuperInternal function (closure). This function returns the createSuperInternal function (closure).

/ / call
var _super = _createSuper(Student)
// Function, Derived
function _createSuper(Derived) {
  // Check whether the current environment supports Reflect
  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)
  }
}
Copy the code

createSuperInternalfunction

Super gets the prototype (above we subclassed __proto__ = parent, so Super = Person constructor). If reflects. construct is used to create a new object (new reflectings in ES6). If Reflect is not supported, use super.apply to create a new object.

  • Reflect.construct()

Finally, call _possibleConstructorReturn, the function is to determine the boundaries, return or create a new object

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)
}
Copy the code

Back to above:

var Student = /*#__PURE__*/ (function(_Person) {
  // the _inherits() function is used to implement inheritance
  _inherits(Student, _Person)
  // _super returns the createSuperInternal function
  var _super = _createSuper(Student)

  function Student() {
    _classCallCheck(this, Student)
	// _super.apply returns the new object created
    return _super.apply(this.arguments)}return Student
})(Person)
Copy the code

7. Inherit built-in classes

We can make our classes inherit from built-in classes, such as Array

class MyArray extends Array {
  constructor(length) {
    super(length)
  }
  set lastValue(value) {
    this[this.length - 1] = value
  }
  get lastValue() {
    return this[this.length - 1]}}// You can use methods in the parent Array class
const arr = new MyArray(10).fill(10)
// You can customize some functions
arr.lastValue = 9
console.log(arr, arr.lastValue)
Copy the code

8. Polymorphism in JS

Object-oriented has three major features: encapsulation, inheritance and polymorphism. The first two have been explained in detail in P10. Let’s talk about polymorphism in JS

A polymorphism refers to the practice of providing a unified interface for entities of different data types or using a single symbol to represent multiple different types

To put it simply: different data types perform the same operation and exhibit different behaviors, which is the manifestation of polymorphism

JS, by definition, exists in polymorphism

8.1 Polymorphism in traditional object-oriented languages

In traditional object-oriented languages such as Java, polymorphism is typically represented by overwriting and overloading.

  • Rewrite, is generally a subclass to rewrite the parent class of a method implementation process, method name, parameter type, parameter number, return value type are the same
  • Method overloading, the same can have different implementation details, the same method name, parameter type, number of parameters is different (overloaded actually belong to do not belong to the most polymorphic view vague, some people think that online overloading only belongs to the polymorphism of function, does not belong to the polymorphism of object-oriented, here by I discern) are you from

ECMAScript does not regulate method overwriting and overloading, and overloading is not even possible in JS, but overwriting is possible

Polymorphism in 8.2 JS

Strictly speaking, polymorphism occurs within a class, but JS is a dynamic language, so it is very flexible, so there will be another form of polymorphism:

Here’s an example to understand:

var baiduMap = {
  render: function () {
    console.log('Render Baidu Map')}},var googleMap = {
  render: function () {
    console.log('Render Google Maps')}},Copy the code

Both maps have the same behavior (both have the same render behavior, the difference is what render does), and writing code in this way makes it easy to find that multiple maps require multiple objects, but the objects all behave the same, resulting in a lot of code redundancy. At this point, we can isolate render, and even two different types can implement polymorphic forms

// Provide a unified interface
var map = {
  render: function (msg) {
    console.log(msg)
  },
}

var googleMap = {
  msg: 'Render Google Maps',}class BaiduMap {
  constructor(msg) {
    this.msg = msg
  }
}
const baiduMap = new BaiduMap('Render Baidu Map')

// Different types trigger unified interfaces that behave differently
map.render(googleMap.msg) // Render Google Maps
map.render(baiduMap.msg) // Render Baidu Map
Copy the code

This code is a representation of polymorphism. Polymorphism is a programming idea that you don’t need to stick to a particular representation, as long as you are satisfied with the definition of polymorphism in Wikipedia

conclusion

In this article, you learned:

  • If you are usingclassDefine a class
  • Class instance methods, static methods, properties
  • How to useextendsTo implement inheritance
  • classthroughbabelConversion to ES5 code performance
  • The representation of polymorphism in JS