简体   繁体   中英

How to implement a “class extending” function (for example a decorator function) in ES5 which also works with ES2015 classes

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"

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.

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM