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 used
class
Keyword 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 internal
this
That points to this empty objectthis
- The class of
prototype
Value 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) class
this
Or return the default object,Must bethroughsuper
Call the parent class’s constructor super
Where: 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
_createClass
Receives three parameters,- The first argument is the target object to mount, which in the code is
Person
. - The second argument takes an array of all instance methods defined, each of which is an object,
key
Is the method name,value
Is 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
_defineProperties
If it is an instance method, it is passedPerson.prototype
And an array of instance methods, if staticPerson
And static method array
- The first argument is the target object to mount, which in the code is
_defineProperties
Receives 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, pass
Object.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
_inherits
function
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
_createSuper
function
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
createSuperInternal
function
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 using
class
Define a class - Class instance methods, static methods, properties
- How to use
extends
To implement inheritance class
throughbabel
Conversion to ES5 code performance- The representation of polymorphism in JS