0

I've asked a similar question yesterday and it was marked as duplicate of
ES6: call class constructor without new keyword

As the above linked document does not give a full answer, I now asked the same question a bit different today ... and provide a possible solution below:

How do I have to implement the ES5 function "extend" (it should just extend a class) in the following demo to also work with ES2015 classes without getting an "Class constructor baseClass cannot be invoked without 'new'" error?

[Edit: The function "extend" will be part of a (transpiled) ES5 library. This library itself may then later be used by someone else who uses either ES5 or >= ES2015 - both cases should work]

// ES5 function (similar to: constr => class extends constr {})
function extend(constr) {
  var ret = function () {
    constr.apply(null, arguments)
  }

  ret.prototype = Object.create(constr.prototype)

  return ret
}


// ES2015 code
const
  baseClass = class {},
  extendedClass = extend(baseClass)

new extendedClass() // <- Error: Class constructor baseClass cannot be invoked without 'new'

See: https://jsfiddle.net/kjf7cheo/

One solution could look like follows (=> using "new Function(...)"). Is there anything wrong/insecure/to be considered with this solution?

var extend = null

if (typeof Proxy === 'function') { // check for ECMAScript version >= 2015
  try {
    extend =
      new Function('baseClass', 'return class extends baseClass {}')
  } catch (e) {
    // ignore
  }
}

if (!extend) {
  extend = function (baseClass) {
    var ret = function () {
      baseClass.apply(this, arguments)
    }

    ret.prototype = Object.create(baseClass.prototype)

    return ret
  }
}

// Result: function "extend"
4
  • If you are dealing with classes already why mix constructor types? Commented Aug 23, 2018 at 2:27
  • The above code will be part of a (transpiled) ES5 library. This library may then later be used by someone else who uses either ES5 or >= ES2015 Commented Aug 23, 2018 at 2:31
  • I don't get it how that makes any difference? If you are using a transpiler then you can do classes, if you are not using a transpiler then it won't be a ES5 environment because you have classes. Unless you want to use this to extend transpiled code? Commented Aug 23, 2018 at 2:49
  • Let's assume the library has a function decorate(baseClass) that uses "extend" internally that can be used either as (ES2015) decorate(class { ... }) or as (ES5) decorate(someES5Constructor) - both should work Commented Aug 23, 2018 at 2:54

2 Answers 2

1

I'd try using Reflect.construct for this:

let extend = function (constr) {
    let ret;
    if(Reflect && Reflect.construct){
        ret = function(...args){
            let inst = Reflect.construct(constr, args, eval('new.target'));
            return inst;
        }
    } else {
        ret = function () {
        constr.apply(this, arguments)
        }
    }
    ret.prototype = Object.create(constr.prototype);
    ret.prototype.constructor = ret;
    return ret;
}

The eval line just makes it so you don't get a syntax error when you transpile it to ES5. It's necessary if you want to extend the constructors you create with extend otherwise the prototypes will be lost.

Sign up to request clarification or add additional context in comments.

6 Comments

Thx for your answer. The problem with this solution is that to following code will output "false" instead of "true": class A {}; const B = extend(A); console.log(new B instanceof B)
@MinusFor: I think it must be "Reflect.construct(constr, args, derived)" in your code snippet above, then it seems to work, correct?
@Natasha oh I was using it incorrectly, it needs an array like object not spreading the arguments.
Note, you do need new.target here so you can actually set the prototype correctly... however that's not valid syntax for ES5. You could wrap it on an eval line though
@MinusFor Thanks, your latest version (=> eval('new.target')) is working great. But now we have the problem that using "eval" is bad, even worse than using "new Function(...)" as I have done in my solution above. Do you have any idea how to replace that "eval('new.target')" thing with something else?
|
0

I think this could be a solution without using "eval(...)" or "new Function(...)" ... what do you think?

Find a demo here: https://jsbin.com/vupefasepa/edit?js,console

var hasReflection =
  typeof Reflect === 'object'
    && Reflect
    && typeof Reflect.construct === 'function'

function extend(baseClass) {
  var ret = function () {
    if (!(this instanceof ret)) {
      throw new Error("Class constructor cannot be invoked without 'new'")
    }

    var type = this.constructor

    return hasReflection
      ? Reflect.construct(baseClass, arguments, type)
      : void(baseClass.apply(this, arguments))
  }

  ret.prototype = Object.create(baseClass.prototype, {
    constructor: {
      value: ret
    }
  })

  return ret
}

1 Comment

I guess this.constructor can work in this case, i can't think of an edge case right now but it seems it works.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.