There was a time when I thought I knew the arrow function too well to be fooled. But a few days ago I encountered a very strange problem, after agonizing for a long time, found that is the arrow function brought the pit. Hence, this article ~

Problem description

Let’s say I have a base class Animal that has a base method sayName. Each subclass that inherits from it then implements the sayName method to prove its identity. The base class code implementation is simple:

class Animal {
	sayName = (a)= > {
		throw new Error('You should implement this method yourself'); }}Copy the code

Now I’m going to implement a Pig subclass from the Animal base class.

class Pig extends Animal {
	sayName() {
		console.log('I am a Pig'); }}Copy the code

Oh, is that so easy? Where is the pit? However, when you actually run, you will find that the results are not as expected:

Ah, why is that? What went wrong? I don’t know why I can get an error with just a few lines of code.

Found the problem

After a bit of fiddling, it was finally found to be the pit of the arrow function. We can solve this problem by simply changing the sayName of the Animal base class to a normal function or the sayName of the Pig subclass to an arrow function. So, what the hell is going on with arrow functions?

At this point, I suddenly remembered that I had been interviewed by an interviewer about this question! What is the difference between the arrow function for a class and the constructor bind function? At that time the answer was well-reasoned, the result of the inheritance of the situation, turned over water water. So to answer this question, let’s answer this interview question first.

What is the difference between the arrow function and the constructor bind function

To get a sense of the problem, we can use Babel’s code compilation results to better see the difference.

Let’s start with a simple piece of code

class A {
  	constructor() {
		this.b = this.b.bind(this);    	
    }
  
    a() {
    	console.log('a');
    }
	  b() {
    	console.log('b')
    }
    c = (a)= > {
    	console.log('c')}}Copy the code

Let’s see what Babel looks like when compiled:

"use strict";

function _instanceof(left, right) { if(right ! =null && typeof Symbol! = ="undefined" && right[Symbol.hasInstance]) { return!!!!! right[Symbol.hasInstance](left); } else { return left instanceofright; }}function _classCallCheck(instance, Constructor) { if(! _instanceof(instance, 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; }

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 A = /*#__PURE__*/function () {
  function A() {
    _classCallCheck(this, A);

    _defineProperty(this."c".function () {
      console.log('c');
    });

    this.b = this.b.bind(this);
  }

  _createClass(A, [{
    key: "a".value: function a() {
      console.log('a'); }}, {key: "b".value: function b() {
      console.log('b'); }}]);returnA; } ();Copy the code

More than half of the compiled code is auxiliary functions, so we can focus on just a few of the highlights:

var A = /*#__PURE__*/function () {
  function A() {
    _classCallCheck(this, A);

    _defineProperty(this."c".function () {
      console.log('c');
    });

    this.b = this.b.bind(this);
  }

  _createClass(A, [{
    key: "a".value: function a() {
      console.log('a'); }}, {key: "b".value: function b() {
      console.log('b'); }}]);returnA; } ();Copy the code

From the compiled results, we can see the differences:

  • Normal functions: when compiled by Babel, they are placed on the function’s prototype
  • Not only is it placed in the function’s prototype after compilation, but each instantiation produces a variable bound to the current instance context (this.b = this.b.bind(this)).
  • Arrow functions: After Babel is compiled, defineProperty is called on each instantiation to bind the arrow function contents to the current instance context.

Based on the compiled results, it is best to use the arrow function for actual development if you need to bind context. Not only does bind generate a prototype function, but each instantiation generates an additional function.

update

Read yu Teng Jing’s comments, to understand more essential things.

Class treats methods and variables declared by = as instance properties, and attributes not declared by = are placed on the prototype chain. Such as

class A {
    a() {
        
    }
    b = 2;
    c = (a)= >{}}Copy the code

For this class, b and C are instantiated as instance properties, and A is placed on the stereotype chain.

So why does this happen? Tc39 Field declarations are a declaration of Field declarations

For instance declarations of equals, that is the syntax of Field declarations, which directly declares an instance property.

Back to the topic

After we’ve solved the last problem, let’s get back to the main point. Now that we know what the arrow function of the class looks like when it’s actually compiled, it’s actually easier to understand our problem.

Q: Why does the execution of a subclass declare sayName as a normal function fail?

A: If A subclass declares sayName as A normal function, the sayName declared by the subclass will be placed on the constructor’s Prototype. However, since the sayName of the base class uses the arrow function, each instance will have a direct sayName variable. According to javascript variable access rules, the variable itself is searched first, and if it cannot be found, the prototype chain is searched. Therefore, when we look for sayName, we will directly find the sayName function declared by the base class, and we will not look for it on the prototype chain, so there is a problem.

Q: Why should subclasses declare sayName as arrow functions and execute without problem?

A: Es6 classes are initialized by executing the base class’s constructor first, and then their own constructor. Therefore, after the base class is initialized, the arrow function sayName declared by the subclass overrides the base class, so there is no problem executing.

conclusion

Once I thought I knew the arrow function well, but I was still fooled. But I also have a deeper understanding of the in-class arrow functions.

The arrow function is not a problem. A class variable declared with the = sign is a Field declarations syntax. Variables declared in this way are actually mounted directly to the properties of the instance, rather than to the prototype chain.

This article address in -> my blog address, welcome to give a start or follow