ES6 Class Source Code Analysis
The last blog about React left a pit about Es6 class, about the relationship between class and function, we will discuss this blog.
We come to this blog with two questions:
- how classes are implemented with functions
What is the difference between new class and new function?
How to implement class with function
Prototype chain
As we all know, class is actually a syntactic sugar of function, which is based on the prototype chain, so if you want to understand this blog, I suggest you to take a look at my previous blog about prototype chain:用公式讲清楚原型链
Using babel to convert classes into es5 code
We can write a piece of class code and then usebabel在线工具Convert it into es5 code, and then analyze it step by step.
First, let’s write a piece of class code
1 | class Person{ |
The function after conversion is too long, I will not completely paste here, want to see the full version can go to the above link to see for yourself, we now begin to analyze the transformation of the function step by step.
Auxiliary function
_inherits
1 | function _inherits(subClass, superClass) { |
The name of this function is used for inheritance, that is, the function will be called when extends.
This function first comes up to determine whether the parent class meets the conditions, if not, directly throw an exception.
This is followed by a call to the Object.create method, which does this:
The Object.create () method creates a new object, using the existing object to provide the proto of the newly created object.
That is to say:
1 | subClass.prototype = Object.create(superClass && superClass.prototype, { |
This code completes inheritance based on the prototype chain. The result of this code is that a new object is created through superClass.prototype, the proto of the new object points to superClass.prototype, and then the object is assigned to subClass.prototype.
That is, subClass.prototype. ‘proto’ = superClass.prototype
In this way, the subclass prototype chain has superclass prototypes.
Then there is the last step of _setPrototypeOf, we continue to see what this function does
_setPrototypeOf
1 | function _setPrototypeOf(o, p) { |
The role of this function is also to construct a prototype chain, and its core purpose is this sentence
1 | o. _ _ proto _ _ = p |
Combined with the previous auxiliary function
1 | _setPrototypeOf(subClass, superClass) |
The result is subClass. ‘proto’ = superClass
_isNativeReflectConstruct
1 | function _isNativeReflectConstruct() { |
To understand this code, it is necessary to understand what Reflect is and what Reflect.construct is
First of all, Reflect is actually a new syntax of ES6. You can see what you can doMDN, is not our current focus, we will only focus on one of them for the time being:
[`Reflect.construct(target,
Construct the function
Then the role of this function is more obvious, that is, to see if the current execution environment can use Reflect.construct to create new objects.
_getPrototypeOf
1 | function _getPrototypeOf(o) { |
This function is the next prototype in the prototype chain that gets passed parameters, which can also be understood as the object it directly inherits.
_typeof
1 | function _typeof(obj) { |
This code first creates the function variable _typeof through if-else. Since it is es5 code, there is no const and let. This direct variable assignment method is actually the same as var, so it will exist变量提升
The result is that a function is declared in the function scope, assigned to _typeof, and then called to return the result.
Putting why there is this judgment, I should leave a pit here again, we temporarily ignore it, just as it is for browser compatibility.
_possibleConstructorReturn
1 | function _possibleConstructorReturn(self, call) { |
First of all, we need to know’void 0 ’ = undefined` 是 true.
Looking back at this code again, that is, if the call is an object or function, return the call directly.
If not, then see if self is undefined. If so, throw an exception. If not, return self.
_createSuper
1 | function _createSuper(Derived) { |
Well, after seeing so many auxiliary functions above, they are finally used here.
Let’s take a good look at what this function does:
- First determine whether the current execution environment can call Reflect.construct, if you can hasNativeReflectConstruct is true.
Then the result of this function execution actually returns the _createSuperInternal of another function, which uses the outer parameters, so this is a closure.
Let’s see what happens in this closure:- Get Derived Prototype as Super
- if the current environment can call Reflect.construct (hasNativeReflectConstruct is true)
- var NewTarget = _getPrototypeOf (this).constructor; Gets the constructor up the prototype chain of the current function executor and assigns it to NewTarget.
- result = Reflect.construct (Super, arguments, NewTarget); call new Super with arguments, the constructor of the created object is NewTarget
- If hasNativeReflectConstruct is false, call apply directly on this, this inheritance method is actually ** borrowing constructor function inheritance ** this inheritance method, there are many other inheritance methods, interested can go to my other blog: https://kingworker.cn/javascript-继承/
- Finally call _possibleConstructorReturn (this, result), the result is, if result is an object or function, then return result, otherwise if this is not undefined, then return this, do not meet the throw error.
_classCallCheck
1 | function _classCallCheck(instance, Constructor) { |
This function is very simple, it is used to detect that the class cannot be called like a normal function, such as class Persion. You cannot directly Persion ()
_defineProperties
1 | function _defineProperties(target, props) { |
The function of these two functions together is to continuously define new properties on the target
_createClass
1 | function _createClass(Constructor, protoProps, staticProps) { |
Combined with the above _defineProperties function, this function has three parameters, the first parameter is the constructor function, the second parameter is an array of all common properties, and the third is an array of all static properties, so the function is actually:
Define the ordinary properties of the class on the prototype of the constructor function, so that when we create a new instance, we can find these ordinary properties on the prototype chain of the instance
Those who have read my previous blog about prototype chains should be impressed with my formula:
a = new A() => a.
__proto__
= A.prototype
- Defines static properties in the class on the constructor function, so that we can directly point out static methods on the constructor function.
Dynamically generated code
Okay, with these auxiliary functions above, I can see how the class we defined at the beginning uses these auxiliary functions to achieve
1 | var Person = /*#__PURE__*/ (function () { |
- First of all, Persion is defined as the return result of an immediately executed function, which is still a function, so we can know that in the end Persion is actually a function, and we still call this function Constructor Persion.
What does the Constructor Persion function do?
First, call _classCallCheck (this, Person);, this is the auxiliary function we just analyzed, to ensure that our Constructor Persion function is not called directly, but placed after new as a constructor function.
then add two properties on the instance, name and age
Call _createClass, we just analyzed, the three parameters of this function are constructor function, array of normal properties, array of static properties, so after this step, Persion.prototype has a new property called sayName, Persion There is a new static property called intro.
Finally back to Constructor Persion
Because type is a static property of Class Persion, define type on Constructor Persion so that it can be found directly on the constructor function.
Men is also defined as the result of an immediately executed function, which is also a function. We call this function Constructor Men.
First call _inherits (Men, _Person), through the “_inherits” function we just analyzed, its function is
subClass.prototype.
__proto__
= superClass.prototypesubClass.
__proto__
= superClassput it here, that is to say
Men.prototype.
__proto__
= _Person.prototypeMen.
__proto__
= _PersonThis completes the inheritance of the prototype chain
One thing to note here is that ** this Men is the function Men of the following declaration, not the outer Men, because the function declaration promotes **
var _super = _createSuper(Men)
We just analyzed this _createSuper function, it will return another function, the role of this function is to get’Men.proto 'as Super, and then call Super with the function caller as the context, and the above _inherits (Men, Person) ’ has made Men. ’ _ pro to __` = _ Person.
So the result of this step is a function that will call the Persion function in the context of the caller
Second, define Constructor Men (although the code order is not like this, but the function declaration will raise the variable), so what does this Constructor Men do?
- Also check if the Men are called directly.
Invoking _super, that is, calling Persion on an instance of Men, is typical of borrowing constructor function inheritance. - Mount the gender attribute on the Men instance.
- Also check if the Men are called directly.
返回Constructor Men
We finally analyzed the results:
- First we get two functions Persion and Men, both of which can only be called by new, otherwise an exception will be thrown.
Secondly, both constructor functions themselves define the declared static properties, and the constructor function prototype defines the normal properties. - Men.prototype.
__proto__
= _Person.prototype - Men.
__proto__
= _Person - The constructor function of Men inherits by calling the method of the Persion constructor function on the Men instance.
new
See here and understand the small partners should understand, there is no difference, just more built-in checks to help you do the inheritance of the prototype chain.
The class essence returns a function.