What is a class
JavaScript is known to have no classes, and classes are just grammatical sugars. This article aims to clarify what grammatical sugars we often hang around our mouths mean.
ES6 versus ES5 Writing
class Parent { static nation = 'China' isAdult = true get thought() { console.log('Thought in head is translate to Chinese.') return this._thought } set thought(newVal) { this._thought = newVal } constructor(name) { this.name = name } static live() { console.log('live') } talk() { console.log('talk') } }
This is a very complete writing, we are used to writing a class so easily, so what is the corresponding writing in ES5?
function Parent(name) { this.name = name this.isAdult = true } Parent.nation = 'China' Parent.live = function() { console.log('live') } Parent.prototype = { get thought() { return this._thought }, set thought(newVal) { this._thought = newVal }, talk: function() { console.log('talk') } }
You can see it clearly
- The constructor of the Parent class in ES6 corresponds to the constructor Parent in ES5.
- The instance attributes name and isAdult, regardless of the way they are written in ES6, are still hung under this in ES5.
- The static properties and methods nation and live modified by the keyword static in ES6 are directly suspended on the class Parent.
- It is worth noting that getter and setter The tought and method talk are hung on the prototype object Parent.prototype.
How Babel is compiled
We can enter the code into the Babel website Try it out To see the compiled code, this part of our step-by-step compilation process breaks down Babel's compilation process:
Process One
We will only look at the compilation of attributes at this point.
Before compilation:
class Parent { static nation = 'China' isAdult = true constructor(name) { this.name = name } }
After compilation:
'use strict' // Encapsulated instanceof operation function _instanceof(left, right) { if ( right != null && typeof Symbol !== 'undefined' && right[Symbol.hasInstance] ) { return !!right[Symbol.hasInstance](left) } else { return left instanceof right } } // ES6 class, must be called with the new operation, // The purpose of this method is to check if the _instanceof method encapsulated above is invoked through the new operation function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError('Cannot call a class as a function') } } // Encapsulated Object.defineProperty function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }) } else { obj[key] = value } return obj } var Parent = function Parent(name) { // Check to see if called through the new operation _classCallCheck(this, Parent) // Initialize isAdult _defineProperty(this, 'isAdult', true) // Initialize name based on attendance this.name = name } // Initialize static property nation _defineProperty(Parent, 'nation', 'China')
From the compiled code, you can see that Babel encapsulates some methods for its rigor, perhaps a little confused is the Symbol.hasInsance in the _instanceof(left, right) method, from MDN and Introduction to ECMAScript6 As you can see from this property, you can customize the behavior of the instanceof operator on a class.There is also a focus on the object_classCallCheck(instance, Constructor), which checks to see if it is called through the new operation.
Process Two
Before compilation:
class Parent { static nation = 'China' isAdult = true get thought() { console.log('Thought in head is translate to Chinese.') return this._thought } set thought(newVal) { this._thought = newVal } constructor(name) { this.name = name } static live() { console.log('live') } talk() { console.log('talk') } }
After compilation:
'use strict' // Encapsulated instanceof operation function _instanceof(left, right) { // ..... } // ES6 class, must be called with the new operation, // The purpose of this method is to check if the _instanceof method encapsulated above is invoked through the new operation function _classCallCheck(instance, Constructor) { // ...... } // Encapsulate Object.defineProperty to add properties function _defineProperties(target, props) { // Traversing props for (var i = 0; i < props.length; i++) { var descriptor = props[i] // enumerable defaults to false descriptor.enumerable = descriptor.enumerable || false descriptor.configurable = true if ('value' in descriptor) descriptor.writable = true Object.defineProperty(target, descriptor.key, descriptor) } } // Add prototype or static properties to Constructor and return function _createClass(Constructor, protoProps, staticProps) { // If it is a prototype property, add it to the prototype object if (protoProps) _defineProperties(Constructor.prototype, protoProps) // If it is a static property, add it to the constructor if (staticProps) _defineProperties(Constructor, staticProps) return Constructor } // Encapsulated Object.defineProperty function _defineProperty(obj, key, value) { // ...... } var Parent = /*#__PURE__*/ (function() { // Add getter/setter _createClass(Parent, [ { key: 'thought', get: function get() { console.log('Thought in head is translate to Chinese.') return this._thought }, set: function set(newVal) { this._thought = newVal } } ]) function Parent(name) { // Check to see if called through the new operation _classCallCheck(this, Parent) // Initialize isAdult _defineProperty(this, 'isAdult', true) // Initialize name based on attendance this.name = name } // Add talk and live methods _createClass( Parent, [ { key: 'talk', value: function talk() { console.log('talk') } } ], [ { key: 'live', value: function live() { console.log('live') } } ] ) return Parent })() // Initialize static property nation _defineProperty(Parent, 'nation', 'China')
Compared with procedure one, Babel generates an additional auxiliary function for _defineProperties(target, props) and _createClass(Constructor, protoProps, staticProps), which are used to add prototype and static attributes, and both data descriptors and access descriptors can be controlled through the Object.defineProperty method.
It is worth noting that all methods in the class in ES6 are non-traversable (enumerable: false), and here is a small detail: if you use TypeScript, when you set the target in compileOptions and es5, you will find that the compiled method can be traversed through Object.keys(), but not when you set it to es6.
summary
Babel parses through an AST abstract grammar tree and adds the following
- _instanceof(left, right)//encapsulated instanceof operation
- _classCallCheck(instance, Constructor)//Check if called through the new operation
- _defineProperties(target, props)//Encapsulate Object.defineProperty to add properties
- _createClass(Constructor, protoProps, staticProps)//Add prototype or static properties to Constructor and return
- _defineProperty(obj, key, value) // /Encapsulated Object.defineProperty
Five auxiliary functions to add properties and methods to the Parent constructor and convert the syntax sugar named class to ES5 code.
What is extends
Since ES6 has no classes, how do you inherit it? Believe the wise you already know that, like classes, extends are grammatical sugars. Next, let's take this grammatical sugars apart step by step.
Parasitic combinatorial inheritance of ES5
from Starting with Prototype (above) - illustrating ES5 inheritance related As you know here, the relatively perfect implementation of inheritance is a parasitic combinatorial inheritance, and for ease of reading, the source code and schematic diagram are attached here again:
function createObject(o) { function F() {} F.prototype = o return new F() } function Parent(name) { this.name = name } function Child(name) { Parent.call(this, name) } Child.prototype = createObject(Parent.prototype) Child.prototype.constructor = Child var child = new Child('child')
ES6 versus ES5 Writing
If we refer to the inheritance implementation above, we can easily write out two versions of inheritance
class Child extends Parent { constructor(name, age) { super(name); // Call the constructor(name) of the parent class this.age = age; } }
function Child (name, age) { Parent.call(this, name) this.age = age } Child.prototype = createObject(Parent.prototype) Child.prototype.constructor = Child
How Babel is compiled
Some details
- The subclass must call the super method in the constructor method, or an error will be reported when creating a new instance.This is because subclasses do not have their own this object, but inherit and process the parent's this object.If you do not call the super method, the subclass will not get the this object.
That's why, in the constructor of a subclass, the this keyword can only be used after super is called, otherwise an error will occur.
- In ES6, static methods of the parent class can be inherited by subclasses.Class is the syntax sugar for the constructor and has both the prototype and u proto_u attributes, so there are two inheritance chains at the same time.
Compilation process
Similarly, we enter the code into the Babel website's Try it out To view the compiled code:
'use strict' // Encapsulated typeof function _typeof(obj) { 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) } // Call the constructor() of the parent class and return this of the child class function _possibleConstructorReturn(self, call) { if ( call && (_typeof(call) === 'object' || typeof call === 'function') ) { return call } return _assertThisInitialized(self) } // Check if super() of subclass is called function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError( "this hasn't been initialised - super() hasn't been called" ) } return self } // Encapsulated getPrototypeOf function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o) } return _getPrototypeOf(o) } // Implement Inherited Auxiliary Functions function _inherits(subClass, superClass) { if (typeof superClass !== '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) } // Encapsulated setPrototypeOf function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p return o } return _setPrototypeOf(o, p) } // Check to see if called through the new operation function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError('Cannot call a class as a function') } } var Child = /*#__PURE__*/ (function(_Parent) { // Inheritance operation _inherits(Child, _Parent) function Child(name, age) { var _this _classCallCheck(this, Child) // Call the constructor() of the parent class and return this of the child class _this = _possibleConstructorReturn( this, _getPrototypeOf(Child).call(this, name) ) // Initialize the subclass's own properties based on participation _this.age = age return _this } return Child })(Parent)
_inherits(subClass, superClass)
Let's take a closer look at the details of this auxiliary function that implements inheritance:
function _inherits(subClass, superClass) { // 1. Check that the inheritance target of extends (that is, the parent class) must be a function or null if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError( 'Super expression must either be null or a function' ) } // 2. Parasitic combinatorial inheritance similar to ES5, using Object.create, // Setting the u proto_u property of the subclass prototype property points to the prototype property of the parent class subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }) // 3. Set the u proto_u property of the subclass to point to the parent class if (superClass) _setPrototypeOf(subClass, superClass) }
This method is divided into three main steps, the second step is to achieve inheritance through parasitic combinatorial inheritance with the addition of a new non-enumerable property called constructor; the third step is to achieve the second prototype chain mentioned above, so that the static method can also be inherited.
_possibleConstructorReturn(self, call)
This auxiliary function is mainly used to achieve the effect of super(), and in the case of parasitic combinatorial inheritance, it is the part inherited by the constructor, unlike this, which returns a this and assigns it to the subclass.Details can be found in How Babel of the ES6 series compiles Class (below) See.
summary
Like class, Babel uses AST abstract grammar tree analysis and then adds a set of auxiliary functions, which I think can be divided into two categories, the first one:
- _typeof(obj)//encapsulated typeof
- _getPrototypeOf(o)//encapsulated getPrototypeOf
- _setPrototypeOf(o, p)//Encapsulated setPrototypeOf
This robust functional auxiliary function
Category 2:
- _assertThisInitialized(self)//Check whether super() of the subclass is called
- _possibleConstructorReturn(self, call)//Call constructor() of the parent class and return this of the child class
- _classCallCheck(instance, Constructor)//Check if called through the new operation
- _inherits(subClass, superClass)//Implements inherited auxiliary functions
This process assistant function is designed to achieve the main functions, thereby achieving a more complete parasitic combined inheritance.
Postnote
Starting with Prototype, there are two articles about JavaScript prototypes from two perspectives.
- Starting with Prototype (above) - illustrating ES5 inheritance related
- Starting with Prototype (below) - class es and extends in ES6