catalog
- preface
- class is a special function
- How class works
- Prototype chain relation of class inheritance
- reference resources
1. Preamble
The JavaScript class introduced in ECMAScript 2015 (ES6) is essentially the existing prototype based inherited syntax sugar of JavaScript. Class syntax does not introduce a new object-oriented inheritance model for JavaScript.
2.class It's a special function
class of ES6 mainly provides more convenient syntax to create old constructor functions. We can get its type through typeof:
class People { constructor(name) { this.name = name; } } console.log(typeof People) // function
What kind of function is the class declared? We can use online tools ES6 to ES5 To analyze the real implementation behind class.
3.class How it works
Next, through the comparison of multiple sets of codes, we will analyze what functions the class declared will be converted into.
Group 1: declare an empty class with class
Syntax of ES6:
class People {}
Here are two questions:
1.class The declared class is not the same as the function declaration and will not be promoted (that is, the use must follow the declaration). Why?
console.log(People) // ReferenceError class People {}
Run the error report in the browser, as shown in the following figure:
2. You can't call the class People() directly like a function call. You must call the class through new, such as new People(). Why?
class People {} People() // TypeError
Run the error report in the browser, as shown in the following figure:
Convert to ES5:
"use strict"; function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } // Judgment Constructor.prototype Whether it appears in the prototype chain of instance object function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var People = function People() { // Check if it is called through new _classCallCheck(this, People); };
For the two questions mentioned above, we can use the transformed ES5 code to solve them:
For problem 1, we can see that the class declared by class is converted into a function expression, and the value of the function expression is saved with the variable People, while the function expression can only be created in the code execution phase and does not exist in Variable object If the class is used before class declaration, it is the same as before the variable People is assigned a value. At this time, the use is meaningless, because its value is undefined, and direct use will report an error. Therefore, ES6 specifies that accessing a class before class declaration will throw a ReferenceError error (the class is undefined).
For question 2, we can see that in the expression of the People function, the_ The classCallCheck function ensures that the People function must be called through new. If People() is called directly, because it is executed in strict mode, this is undefined at this time, call_ The instanceof function checks the inheritance relationship and its return value must be false, so a TypeError error must be thrown.
Supplement: the body of class declaration and class expression are executed in Strict mode Next. For example, constructors, static methods, prototype methods, getter s, and setter s are all executed in strict mode.
Group 2: add public and private fields to the class
Syntax of ES6:
class People { #id = 1 // Private field. It starts with a single '#' character name = 'Tom' // Public fields }
Convert to ES5:
... // Mapping public fields of a class to properties of an instance object 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 People = function People() { _classCallCheck(this, People); // Initialize private fields _id.set(this, { writable: true, value: 1 }); // Mapping public fields of a class to properties of an instance object _defineProperty(this, "name", 'Tom'); }; // Private field after conversion (naming conflicts will be checked automatically) var _id = new WeakMap();
Comparing the codes before and after transformation, we can see that:
For private fields, when class is used to declare private fields, the Convention is to start with the character '#', and replace '#' in the identifier with 'after conversion_ 'and use one alone WeakMap type In this way, the instance object of the class cannot directly access the private field through the property (the private field is not in the property of the instance object at all).
For public fields, the_ The defineproperty function maps the public fields of a class to the properties of an instance object. If it is set for the first time, it also uses the Object.defineProperty Function to initialize and set the enumerable, configurable and writable properties
Group 3: add constructor and instance properties to the class
Syntax of ES6:
class People { #id = 1 // Private field. It starts with a single '#' character name = 'Tom' // Public fields constructor(id, name, age) { this.#id = id this.name = name this.age = age // Instance property age } }
Convert to ES5:
... // Set (modify) the private field of the class function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to set private field on non-instance"); } if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } return value; } var People = function People(id, name, age) { _classCallCheck(this, People); _id.set(this, { writable: true, value: 1 }); _defineProperty(this, "name", 'Tom'); // constructor starts from here _classPrivateFieldSet(this, _id, id); this.name = name; this.age = age; }; var _id = new WeakMap();
Comparing the codes before and after transformation, we can see that:
The execution time of the code in the constructor of the class is after the field definition (the field is mapped to the property of the instance object). The assignment (modification) of the private field is made through_ The classPrivateFieldSet function.
Group 4: add prototype and static methods to the class
Syntax of ES6:
class People { #id = 1 name = 'Tom' constructor(id, name, age) { this.#id = id this.name = name this.age = age } // Prototype method getName() { return this.name } // Static method static sayHello() { console.log('hello') } }
Convert to ES5:
... // Set properties of objects 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); } } // Mapping the methods of a class to the prototype of a constructor( Constructor.prototype )On the properties of // Map the static methods of a class to the properties of a Constructor function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var People = function () { function People(id, name, age) { // ... } // Set methods and static methods of the class _createClass(People, [{ key: "getName", value: function getName() { return this.name; } }], [{ key: "sayHello", value: function sayHello() { console.log('hello'); } }]); return People; }(); var _id = new WeakMap();
Comparing the converted codes of the third group and the fourth group, we can find that:
-
Class through_ The defineProperty function maps to the properties of the instance object (this).
-
Methods of the_ The prototype of the createClass function mapped to the constructor( Constructor.prototype )On the properties of,
-
The static side of the class also passes_ The createClass function maps to the properties of the Constructor.
Group 5: class inheritance
Syntax of ES6:
// Superclass (superClass) class People {} // Subclass inherits the parent class Man extends People {}
Convert to ES5:
... var People = function People() { _classCallCheck(this, People); }; var Man = function (_People) { // Man inheritance_ People _inherits(Man, _People); // Get the constructor of Man's parent class var _super = _createSuper(Man); function Man() { _classCallCheck(this, Man); // This implements the call of the constructor of the parent class. This of the child class inherits the property on this of the parent class return _super.apply(this, arguments); } return Man; }(People);
At_ In inherits function, prototype chain and static attribute inheritance are realized:
// Implement inheritance relationship function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } // Object.create(proto, propertiesObject) method // Create a new object, use proto to provide the__ proto__ // Add the propertiesObject property to the non enumeration (default) property of the newly created object (that is, the property defined by itself, not the enumeration property on its prototype chain) subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } // Prototype object o (that is__ proto__ Property) is p function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
1. Pass Object.create The function call shows that:
(1) subClass.prototype.__proto__ === superClass.prototype , which is equivalent to the inheritance of prototype chain
(2) subClass.prototype.constructor ===Subclass, indicating that the constructor property of the subclass constructor's display prototype object points to the original constructor
2. By calling_ setPrototypeOf(subClass, superClass):
(1) subClass.__ proto__ ===Superclass, equivalent to the implementation of static attribute inheritance
In the Man constructor, by calling the constructor (_super) of its parent class, this of the child class inherits the property on this of the parent class:
// Get the constructor of the parent class function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { 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); }; } // Determine the type of call and return the appropriate Constructor function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } // Assert whether selft is initialized function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } // Determine whether Reflect can be used function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } // Get the proto type of the o object function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
It can be seen from the above that the implementation of class inheritance mainly includes three parts:
- Inheritance of prototype chain
- Inheritance of static properties
- By calling the constructor of the parent class, get the property on this constructor of the parent class
4. Prototype chain relationship of class inheritance
Instance code:
class People { constructor(name) { this.name = name } } class Man extends People { constructor(name, sex) { super(name) this.sex = sex } } var man = new Man('Tom', 'M')
According to the above analysis, we know the implementation principle of class inheritance, and combine In depth understanding of objects in JS (1): prototype, prototype chain and constructor For the prototype chain relationship of the constructor mentioned in, the complete prototype chain relationship of the example code can be obtained as follows:
5. Reference
Why is the class of ES6 grammar sugar?