Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constr.apply(this, args) in ES6 classes

I have used the following function to create instances of unknown classes for some time:

Kernel.prototype._construct = function (constr, args) {
  function F() {
    constr.apply(this, args); // EXCEPTION!
  }
  F.prototype = constr.prototype;
  return new F();
};

If I use prototypes everything works:

function Person(name, surname) {
  this.name = name;
  this.surname = surname;
}

var person = Kernel._construct(Person, ["name", "surname"]); // WORKS!

However, some people are using my library using ES6 native classes in node v4+:

class Person {
  constructor(name, surname) {
    this.name = name;
    this.surname = surname;
  }
}

var person = Kernel._construct(Person, ["name", surname]); // EXCEPTION!

They are getting an error:

TypeError: Class constructors cannot be invoked without 'new'

I need to be able to invoke the constructor with an unknown number of arguments. Any ideas about how to get around this issue?

like image 531
Remo H. Jansen Avatar asked Oct 18 '15 01:10

Remo H. Jansen


People also ask

How do I create a class in ES6?

Prior to ES6, creating a class was a fussy affair. Classes can be created using the class keyword in ES6. Classes can be included in the code either by declaring them or by using class expressions. The class keyword is followed by the class name.

What is the difference between includes () and rest () methods in ES6?

ES6 allows function parameters to have default values. The rest parameter (...) allows a function to treat an indefinite number of arguments as an array: The includes () method returns true if a string contains a specified value, otherwise false: let text = "Hello world, welcome to the universe.";

How to invoke the parent class data member in ES6?

ES6 enables a child class to invoke its parent class data member. This is achieved by using the super keyword. The super keyword is used to refer to the immediate parent of a class. The doPrint () redefinition in the class StringWriter, issues a call to its parent class version.

How do you use expressions in ES6?

expression − Starting with ES6, you can also use expressions as a property name to bind to the given function. The above example defines a class Student with three properties namely rno, fname and lname. The getter function fullName () concatenates the fname and lname and returns a new string.


1 Answers

There are various ways you can do that:

  1. Using Function object's methods:

    Kernel.prototype._construct = function (constr, args) {
      return new (Function.prototype.bind.apply(constr, [null].concat(args)));
    };
    

    Here we're applying args as arguments for bind. The goal is to have a function that can be called without arugments so that we can call new x(). bind does this for us, but we need to set it up correctly. The syntax is:

    func.bind(thisArg[, arg1][, args2...])
    // calling the results from the above is like
    // thisArg.func(arg1, arg2...);
    

    We want to use constr as the function to bind, and the items in args as the arguments. We don't care about thisArg. To do that, we need to "convert" the args array to arguments. The apply call does that:

    func.apply(thisArg[, args]);
    // calling the results from the above is like
    // thisArg.func(args[0], args[1]...);
    

    apply is actually calling bind. The first item,[null], is important because we want to call bind where thisArg is null- like this: constr.bind(null, args[0], args[1]...).

  2. Using ES2015 Spread operator:

    Kernel.prototype._construct = function (constr, args) {
      return new constr(...args);
    };
    

    This is much simpler, but there are 2 problems:

    1. It requires ES2015 support, and even the latest Node (v4.2.1) requires a flag for this (--harmony_spreadcalls).
    2. This will generate a syntax error if not supported, and you can't even do that conditionally, other then using dynamic scripting (eval() / new Function(...)) - which is not advised.
  3. Using the Reflect built-in in object.

    Kernel.prototype._construct = function (constr, args) {
        return Reflect.construct(constr, args);
    };
    

    This is also simple, but is even further behind in terms of support (basically you must use babel).

like image 86
Amit Avatar answered Sep 24 '22 23:09

Amit