Use ES6 new Target to simulate abstract classes

Posted by lox on Thu, 27 Jan 2022 03:03:15 +0100

Recently, we found that symbol is a unique value, but symbol cannot perform new operation. It can only be used as a function. Type errors will occur when running new

new Symbol()

// error
Uncaught TypeError: Symbol is not a constructor
    at new Symbol (<anonymous>)
    at <anonymous>:1:1

If the underlying implementation is not considered, is it possible to implement a function that can only be called but cannot be called at the code level? After thinking, write the following code:

function disConstructor() {
  if (this !== window) {
    throw new TypeError(' disConstructor is not a constructor')
  }
  console.log('gogo go')
}

// The test results are as follows
disConstructor() // gogo go

new disConstructor()

// error
Uncaught TypeError:  disConstructor is not a constructor
    at new disConstructor (<anonymous>:3:15)
    at <anonymous>:1:1

When using nodejs, window can switch to global, and the execution result of the code remains unchanged. This is because there is no applicable scenario for individuals. Therefore, although there is no further research, recently, rereading es 6 found new target.

new.target attribute

Introduction (reference mdn documents)

new. The target attribute allows you to detect whether a function or constructor is called through the new operator. In a function or constructor initialized by the new operator, new Target returns a reference to a constructor or function. In a normal function call, new The value of target is undefined.

In this way, our code can be changed to:

function disConstructor() {
  // In ordinary function calls, new The value of target is undefined.
  if (new.target) {
    throw new TypeError(' disConstructor is not a constructor')
  }
  console.log('gogo go')
}

Get the same answer as the above code.

thorough

Is the function specially added by es6 only used to check our function call mode? In the process of consulting, it is found that most of the schemes use new Target writes out classes that can only be inherited. It is similar to an abstract class that implements java.

class Animal {
  constructor(name, age) {
    if (new.target === Animal) {
      throw new Error('Animal class can`t instantiate');
    }
    this.name = name
    this.age = age
  }
  // Other codes
  ...
}

class Dog extends Animal{
  constructor(name, age, sex) {
    super(name, age)
    this.sex = sex
  }
}

new Animal()
// error
Uncaught Error: Animal class can`t instantiate
    at new Animal (<anonymous>:4:13)
    at <anonymous>:1:1

new Dog('mimi', 12, 'common')
// Dog {name: "mimi", age: 12, sex: "male"}

However, java abstract classes and methods need to be rewritten, and there is no solution. Therefore, in the process of testing and using, it was unexpectedly found that the superclass can access the prototype of the derived class during construction and make use of it.

class Animal {
  constructor(name, age) {
    console.log(new.target.prototype)
  }
  // Other codes
  ...
}

This is how the error report of calling the method to be rewritten by the runtime is written.

class Animal {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  getName () {
    throw new Error('please overwrite getName method')
  }
}

class Dog extends Animal{
  constructor(name, age, sex) {
    super(name, age)
    this.sex = sex
  }
}

const pite = new Dog('pite', 2, 'common')
a.getName()
// error
Uncaught Error: please overwrite getName method
    at Dog.getName (<anonymous>:8:11)
    at <anonymous>:1:3

However, at this time, use new Target, I can use the operation of subclasses during construction to report errors.

class Animal {
  constructor(name, age) {
    // If the target is not a base class and there is no getName, an error is reported
    if (new.target !== Animal && !new.target.prototype.hasOwnProperty('getName')) {
      throw new Error('please overwrite getName method')
    }
    this.name = name
    this.age = age
  }
}

class Dog extends Animal{
  constructor(name, age, sex) {
    super(name, age)
    this.sex = sex
  }
}

const pite = new Dog('pite', 2, 'common')
// error
Uncaught Error: please overwrite getName method
    at new Animal (<anonymous>:5:13)
    at new Dog (<anonymous>:14:5)
    at <anonymous>:1:1

At this time, the errors occurred during the operation of the method can be advanced to the construction period. Although they are all in the operation period, the error trigger mechanism should be early and the harm should be great. Instead, it is a kind of protection for the code.

Of course, using superclasses to access the prototype of derived classes during construction is far from simple. It must be very powerful. We can talk about understanding and role in combination with business scenarios.

Topics: Javascript