ECMAScript6
implements class
, which is actually syntactic sugar, but it's there to make JS
coding clearer and closer to object-oriented programming
.
Implementation principles
First let's look at the implementation of class
in ES6
and the implementation of the ES5
constructor. It's easy to see that constructor
is actually a constructor method, pointing to the ES5
constructor.
ES6
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static run() {
console.log("run");
}
say() {
console.log("hello!");
}
}
ES5
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log("hello!");
};
Person.run = function () {
console.log("run");
};
Babel compiler analysis
After converting ES6
code to ES5
code with the babel
compiler (for code conversion try the official babel online tool, we can get these two key functions _defineProperties
and _ createClass
, now let's explain them one by one.
...
var Person = /*#__PURE__*/ (function () {
"use strict";
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(
Person,
[
{
key: "say",
value: function say() {
console.log("hello!");
},
},
],
[
{
key: "run",
value: function run() {
console.log("run");
},
},
]
);
return Person;
})();
_createClass
The _createClass
function is mainly used to configure public functions and static methods on a constructor or constructor prototype, and return the constructor itself.
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
_defineProperties
The _defineProperties
function is mainly used to declare descriptors for public functions and static methods and mount them to the current constructor or constructor prototype. It takes two arguments target
() and props
.
target
points to the current constructor or constructor prototypeprops
array type, pointing to public functions and static methods
When traversing arrays, we can see that enumerable
is false
by default, meaning that internal properties on class
classes are not enumerable by default and cannot be traversed using Object.keys
, as follows.
Object.keys(Person.prototype); // []
Object.keys(Person); // []
Also during the traversal, it will determine if the value
value exists for the current descriptor, and if so, set the writable property writable
to true
, and use the get
and set
properties otherwise. At the end of the traversal, the descriptor is configured to the current constructor or constructor prototype via Object.defineProperty`'', and this is the basic implementation of
class`''.
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);
}
}
Difference between constructors
Temporary dead zone
class
does not declare lifting, there is a transitory dead zone
, constructors are essentially functions, function declarations will have a lifting effect.
// ๐
{
const foo = new Foo();
function Foo() {}
}
// error
{
const foo = new Foo(); // Cannot access 'Foo' before initialization
class Foo {}
}
Strict mode
The class
declaration internally enables strict mode
by default, and the constructor defaults to non-strict mode
.
// ๐
{
const foo = new Foo();
function Foo() {
size = 20;
}
}
// error
{
class Foo {
constructor() {
size = 20; // size is not defined
}
}
const foo = new Foo();
}
Internal methods are not enumerable
All methods of class
(including static methods
, instance methods
) are not enumerable
, as mentioned in the _defineProperties
function method implementation above, see above for details, and the constructor can enumerate all methods.
{
function Foo() {}
Foo.print = function () {};
Foo.prototype.format = function () {};
console.log(Object.keys(Foo)); // [ "print" ]
console.log(Object.keys(Foo.prototype)); // [ "format" ]
}
{
class Foo {
constructor() {}
static print() {}
format() {}
}
console.log(Object.keys(Foo)); // []
console.log(Object.keys(Foo.prototype)); // []
}
Object prototype
All methods of class
(including static methods
, instance methods
) do not have a prototype object prototype
, and therefore do not have a [[construct]]
, and cannot be called by new
, while constructors support new
calls.
// ๐
{
function Foo() {}
Foo.prototype.format = function () {};
const foo = new Foo();
const fooFormat = new foo.format();
}
// error
{
class Foo {
static print() {}
format() {}
}
const foo = new Foo();
const fooFormat = new foo.format(); // foo.format is not a constructor
const fooPrint = new foo.print(); // foo.print is not a constructor
}
New calls
class
must be called using new
. Constructors are essentially functions and support direct calls.
// ๐
{
function Foo() {}
const foo = Foo();
}
// error
{
class Foo {}
const foo = Foo(); // Class constructor Foo cannot be invoked without 'new'
}
Class name overrides
The class name cannot be overridden inside class
, the constructor can be changed at will.
// ๐
{
function Foo() {
Foo = "yo";
}
const foo = new Foo();
}
// error
{
class Foo {
constructor() {
// Foo = 'yo' // TypeError: Assignment to constant variable
}
}
const foo = new Foo();
Foo = "yo"; // ๐
}