What is theclass
As we all know, JavaScript has no classes and classes are just syntactic sugar. This article aims to clarify what we mean by syntactic sugar.
ES6
与 ES5
Writing contrast
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')}}Copy the code
This is a pretty complete notation, and we’re used to writing a class this easily, but what about the notation 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')}}Copy the code
You can see it very clearly
ES6
中Parent
Of the classconstructor
And that corresponds toES5
Constructor inParent
;- Instance attributes
name
和isAdult
, no matter inES6
Which is written inES5
In is still hanging onthis
Under; ES6
Through the keywordstatic
Modified static properties and methodsnation
和live
, are hung directly in the classParent
On;- It’s worth notingGetter and setter
tought
And methodstalk
Is hung on the prototype objectParent.prototype
On the.
Babel
How is it compiled
We can see the compiled code by typing it into the Try It Out section of the Babel website. We will compile the code step by step and disassemble the Babel compilation process:
Process a
At this point, we only observe the compile-related results of the attribute, before compiling:
class Parent {
static nation = 'China'
isAdult = true
constructor(name) {
this.name = name
}
}
Copy the code
The compiled:
'use strict'
// Encapsulate the 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
}
}
// The ES6 class must be called with the new operation,
// This method checks to see if the _instanceof method encapsulated above is called with the new operation
function _classCallCheck(instance, Constructor) {
if(! _instanceof(instance, Constructor)) {throw new TypeError('Cannot call a class as a function')}}// Wrapped 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 whether the call is made through the new operation
_classCallCheck(this, Parent)
// Initialize isAdult
_defineProperty(this.'isAdult'.true)
// Initializes name based on the input parameter
this.name = name
}
// Initialize the static attribute nation
_defineProperty(Parent, 'nation'.'China')
Copy the code
As you can see from the compiled code, Babel has wrapped some methods for rigor. One of the methods that may be a little confusing is the Symbol. HasInsance in the _instanceof(left, right) method, As you can see from the MDN and ECMAScript6 primer, this property can be used to customize the behavior of the Instanceof operator on a class. There is also a focus on the object _classCallCheck(instance, Constructor), which checks whether it is called by the new operation.
Process 2
Compile the front:
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')}}Copy the code
The compiled:
'use strict'
// Encapsulate the instanceof operation
function _instanceof(left, right) {
/ /...
}
// The ES6 class must be called with the new operation,
// This method checks to see if the _instanceof method encapsulated above is called with the new operation
function _classCallCheck(instance, Constructor) {
/ /...
}
// Encapsulate Object.defineProperty to add attributes
function _defineProperties(target, props) {
/ / traverse the 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 a prototype or static attribute to Constructor and return
function _createClass(Constructor, protoProps, staticProps) {
// If it is a stereotype property, add it to the stereotype object
if (protoProps) _defineProperties(Constructor.prototype, protoProps)
If it is static, add it to the constructor
if (staticProps) _defineProperties(Constructor, staticProps)
return Constructor
}
// Wrapped 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 whether the call is made through the new operation
_classCallCheck(this, Parent)
// Initialize isAdult
_defineProperty(this.'isAdult'.true)
// Initializes name based on the input parameter
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 the static attribute nation
_defineProperty(Parent, 'nation'.'China')
Copy the code
Compared to procedure 1, Babel generates an additional helper function for _defineProperties(target, props) and _createClass(Constructor, protoProps, staticProps). These are mainly used to add stereotype and static attributes, and both data descriptors and access descriptors can be controlled through object.defineProperty methods. It’s worth noting that every method in ES6’s class is an enumerable method. False), here is a small detail: If you use TypeScript, when you set target in compileOptions to ES5, you will find that compiled methods can be traversed through object.keys () but not through ES6.
conclusion
Babel parses through the AST abstract syntax tree and then adds the following
_instanceof(left, right) // Encapsulated instanceof operation
_classCallCheck(instance, Constructor) // Check whether the new operation is invoked
_defineProperties(target, props) // Encapsulates Object.defineProperty to add attributes
_createClass(Constructor, protoProps, staticProps) // Add a prototype or static attribute to Constructor and return
_defineProperty(obj, key, value) // // Encapsulated object.defineProperty
Five helper functions to add attributes and methods to the Parent constructor and convert the syntactic class to ES5 code.
What is theextends
Since ES6 doesn’t have classes, how does inheritance work? As you already know, extends, like class, is a syntactic sugar. Let’s break it down step by step.
ES5
Parasitic combinatorial inheritance
A relatively perfect inheritance implementation is parasitic combinatorial inheritance. For ease of reading, I’ve attached the source code and schematic diagram 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')
Copy the code
ES6
和 ES5
Writing contrast
If we look at the inheritance implementation above, we can easily write two versions of the inheritance form
class Child extends Parent {
constructor(name, age) {
super(name); // Call parent class constructor(name)
this.age = age; }}Copy the code
function Child (name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = createObject(Parent.prototype)
Child.prototype.constructor = Child
Copy the code
Babel
How is it compiled
Some of the details
- Subclasses must be in
constructor
Method callsuper
Method, or an error will be reported when creating a new instance. This is because subclasses don’t have their ownthis
Object, but inherits from its parent classthis
Object, and then process it. If you don’t callsuper
Method, subclasses don’t get itthis
Object. For this reason, in the constructor of a subclass, there are only callssuper
After that, it can be usedthis
Keyword, otherwise an error will be reported.- in
ES6
Static methods of the parent class can be inherited by subclasses.class
Syntactic sugar as constructor, also hasprototype
Properties and__proto__
Property, so there are two inheritance chains.
The build process
Again, we type the code into Try It Out on Babel’s website to see the compiled code:
'use strict'
// Encapsulate 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 parent class's constructor() and return the subclass's this
function _possibleConstructorReturn(self, call) {
if (
call &&
(_typeof(call) === 'object' || typeof call === 'function')) {return call
}
return _assertThisInitialized(self)
}
// Check if the subclass's super() is called
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called")}return self
}
// Encapsulate getPrototypeOf
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o)
}
return _getPrototypeOf(o)
}
// Implement the inherited helper function
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)
}
// Encapsulate setPrototypeOf
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p
return o
}
return _setPrototypeOf(o, p)
}
// Check whether the call is made 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) {
// Inherit operations
_inherits(Child, _Parent)
function Child(name, age) {
var _this
_classCallCheck(this, Child)
// Call the parent class's constructor() and return the subclass's this
_this = _possibleConstructorReturn(
this,
_getPrototypeOf(Child).call(this, name)
)
// Initialize the subclass's own attributes based on the input parameter
_this.age = age
return _this
}
return Child
})(Parent)
Copy the code
_inherits(subClass, superClass)
Let’s take a closer look at the details of the helper function that implements inheritance:
function _inherits(subClass, superClass) {
Check that the object of extends's inheritance (the parent class) must be a function or null
if (typeofsuperClass ! = ='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,
// Set the __proto__ attribute of the protoclass to the prototype attribute of the parent class
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true.configurable: true}})// 3. Set the __proto__ attribute of the subclass to the parent class
if (superClass) _setPrototypeOf(subClass, superClass)
}
Copy the code
This method is divided into three main steps. In the second step, the implementation of inheritance via parasitic combinatorial inheritance adds an unenumerable attribute named constructor. Step 3 implements the second chain of stereotypes mentioned above so that static methods can be inherited.
_possibleConstructorReturn(self, call)
This helper function is used to implement the effect of super(), and for parasitic combinatorial inheritance it borroys the constructor inheritance. The difference is that this returns a this and assigns it to the subclass’s this. Details can be found in the ES6 series on how Babel compiles classes (below).
conclusion
Like class, Babel is parsed through an AST abstract syntax tree and then adds a set of helper functions, which I think can be divided into two categories:
_typeof(obj) // Encapsulated Typeof
_getPrototypeOf(o) // Encapsulated getPrototypeOf
_setPrototypeOf(o, p) // Encapsulated setPrototypeOf
This kind of functional auxiliary function for robustness is the second kind:
_assertThisInitialized(self) // Checks if the super() of the subclass is called
_possibleConstructorReturn (self, call) / / call the superclass constructor (), and return a subclass of this
_classCallCheck(instance, Constructor) // Check whether the new operation is invoked
_inherits(subClass, superClass) // Implements the inherits helper function
This process helper function for the main function enables a more complete parasitic composite inheritance.
Afterword.
Starting with Prototype, there are two parts to describe JavaScript prototypes from two perspectives.
- Start with Prototype (part 1) – Illustrates ES5 inheritance
- Start with Prototype — Class and extends in ES6
The resources
- How does ES6 series Babel compile classes?
- How does ES6 series Babel compile classes?