【问题标题】:constr.apply(this, args) in ES6 classesES6 类中的 constr.apply(this, args)
【发布时间】:2016-01-16 13:18:09
【问题描述】:

一段时间以来,我一直使用以下函数来创建未知类的实例:

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

如果我使用原型,一切正常:

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

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

但是,有些人在 node v4+ 中使用我的库,使用 ES6 本机类:

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

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

他们得到一个错误:

TypeError: Class constructors cannot be invoked without 'new'

我需要能够使用未知数量的参数调用构造函数。有关如何解决此问题的任何想法?

【问题讨论】:

  • 代码中_bind的用途是什么?
  • 我已经删除了,因为我正在使用 _bind 进行测试...

标签: javascript node.js oop constructor ecmascript-6


【解决方案1】:

您可以通过多种方式做到这一点:

  1. 使用Function对象的方法:

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

    这里我们将applying args 作为bind 的参数。目标是有一个可以在没有参数的情况下调用的函数,以便我们可以调用new x()bind 为我们做了这个,但我们需要正确设置它。语法是:

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

    我们想使用constr 作为绑定函数,args 中的项目作为参数。我们不关心thisArg。为此,我们需要将args 数组“转换”为参数。 apply 调用就是这样做的:

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

    apply 实际上是在调用bind。第一项[null]很重要,因为我们想调用bind,其中thisArgnull——就像这样:constr.bind(null, args[0], args[1]...)

  2. 使用 ES2015 Spread operator:

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

    这样简单多了,但是有2个问题:

    1. 它需要 ES2015 支持,甚至最新的 Node (v4.2.1) 也需要一个标志 (--harmony_spreadcalls)。
    2. 如果不支持,这将产生语法错误,您甚至不能有条件地执行此操作,除非使用动态脚本 (eval() / new Function(...)) - 不建议这样做。
  3. 使用Reflect 内置对象。

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

    这也很简单,但在支持方面更落后(基本上你必须使用 babel)。

【讨论】:

  • 感谢您的精彩回答,选项一解决了我的问题 :)
  • 应该注意的是,1 仅适用于原生类,只有原生 Function.prototype.bind 实现。如果不小心被 polyfill 替换了,仍然会抛出 TypeError(转译的类在任何实现中都可以)。
  • 很好的答案。解决方案 1 真的是杀手锏,是我唯一的选择
【解决方案2】:

您可以通过将参数绑定到函数然后对其调用 new 来解决此问题:

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

【讨论】:

  • 这是个好主意,但您的解决方案不准确。看我的回答。
【解决方案3】:

问得好,但是,根据ECMAScript §9.22,在ES6中你不能调用没有new关键字的构造函数。

我在这里问了一个相同的问题: ES6: call class constructor without new keyword

我需要能够使用未知数量的参数调用构造函数。有关如何解决此问题的任何想法?

这并没有说明(不)使用 new 关键字,但是您可以轻松地在 ES6 构造函数中使用可变参数

class Person {
  constructor(name, surname, ...others) {
    this.name = name;
    this.surname = surname;
    this.others = others || [];
  }
}

let p = new Person('OweR', 'ReLoaDeD', 'a', 'b', 'c');
p;
//=> {"name":"OweR","surname":"ReLoaDeD","others":["a","b","c"]}

我怀疑这不是你想要的。但是,如果您试图避免使用new 来调用 ES6 中的类,则无法做到。


那么,话虽如此,您实际上想要完成什么?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多