So es6 class syntax candy is so easy~

Photo by Michael Sum on Unsplash

So es6 class syntax candy is so easy~

ยท

4 min read

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 prototype
  • props 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 ofclass`''.

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"; // ๐Ÿ‘Œ
}

Did you find this article valuable?

Support Jweboy's blog by becoming a sponsor. Any amount is appreciated!

ย