【问题标题】:Why doesn't JavaScript ES6 support multi-constructor classes?为什么 JavaScript ES6 不支持多构造函数类?
【发布时间】:2015-09-17 09:09:46
【问题描述】:

我想像下面这样编写我的 Javascript 类。

class Option {
    constructor() {
        this.autoLoad = false;
    }

    constructor(key, value) {
        this[key] = value;
    }

    constructor(key, value, autoLoad) {
        this[key] = value;
        this.autoLoad = autoLoad || false;
    }
}

如果我们能以这种方式写出类,我认为会很好。 预计会发生:

var option1 = new Option(); // option1 = {autoLoad: false}
var option2 = new Option('foo', 'bar',); // option2 = {foo: 'bar'}
var option3 = new Option('foo', 'bar', false); // option3 = {foo: 'bar', autoLoad: false}

【问题讨论】:

  • “为什么不”通常是一个非常无用的问题。因为,嗯,它没有。许多语言没有,ES6 不是唯一的。
  • 你不需要一个重载的构造函数。只需使用默认值声明 autoLoad
  • method overloading in Javascript 的可能重复项(在 class 中没有什么不同,除了多个定义是语法错误)
  • 因为 JavaScript 到目前为止还没有并且需要这样的概念(即重载)。为什么constructor 是个例外?
  • 几乎相同的问题:为什么 JavaScript 对象不允许您为一个键指定多个值?

标签: javascript class constructor ecmascript-6


【解决方案1】:

我想像下面这样编写我的 Javascript 类

你不能,就像你不能重载那样的标准函数一样。您可以做的是使用arguments 对象来查询传递的参数数量:

class Option {
    constructor(key, value, autoLoad) {
        // new Option()
        if(!arguments.length) {
            this.autoLoad = false;
        }
        // new Option(a, [b, [c]])
        else {
            this[key] = value;
            this.autoLoad = autoLoad || false;
        }
    }
}

Babel REPL Example

当然(使用您更新的示例),您可以采用您不关心参数数量的方法,而不是每个单独的值是否被传递,在这种情况下,您可以这样做类似:

class Option {
    constructor(key, value, autoLoad) {
        if(!key) { // Could change this to a strict undefined check
            this.autoLoad = false;
            return;
        }
        this[key] = value;
        this.autoLoad = autoLoad || false;
    }
}

【讨论】:

  • 为什么我们如此坚决地拒绝使用参数默认值?
  • @torazaburo 我们不是。 正在回答如何模拟重载的问题。我可能错过了原始代码其他方面的一些优化,参数默认值是我认为可能是多个的参数之一。
  • 使用 OR 双管道 || 来设置默认值通常不是好的做法。特别是因为 es6 支持它 ootb。 codereadability.com/…
【解决方案2】:

您想要的称为构造函数重载。 ECMAScript 不支持这种情况,以及更一般的 function overloading 情况。

ECMAScript 处理缺失参数的方式与更严格的语言不同。缺少参数的值保留为undefined,而不是引发错误。在这种范式中,很难/不可能检测到您的目标是哪个重载函数。

惯用的解决方案是拥有一个函数并让它处理您需要的所有参数组合。对于原始示例,您可以像这样测试keyvalue 的存在:

class Option {
  constructor(key, value, autoLoad = false) {
    if (typeof key !== 'undefined') {
      this[key] = value;
    }
    this.autoLoad = autoLoad;
  }
}

【讨论】:

  • 如果他使用class,他使用的是ES6。 ES6 有参数默认值。使用它们。
  • @torazaburo:默认值不适用于可变参数函数
【解决方案3】:

另一种选择是允许您的构造函数采用绑定到您的类属性的对象:

class Option {
  // Assign default values in the constructor object 
  constructor({key = 'foo', value, autoLoad = true} = {}) {
      this.key = key;
      // Or on the property with default (not recommended)
      this.value = value || 'bar';
      this.autoLoad = autoLoad;
      
      console.log('Result:', this);
  }
}

var option1 = new Option();
// Logs: {key: "foo", value: "bar", autoLoad: true}

var option2 = new Option({value: 'hello'});
// Logs: {key: "foo", value: "hello", autoLoad: true}

这对 Typescript 更有用,因为您可以使用传入的值确保类型安全(即 key 只能是字符串,autoLoad 只能是布尔值等)。

【讨论】:

    【解决方案4】:

    从您的示例代码中猜测,您所需要的只是为您的参数使用默认值:

    class Option {
        constructor(key = 'foo', value = 'bar', autoLoad = false) {
            this[key] = value;
            this.autoLoad = autoLoad;
        }
    }
    

    话虽如此,构造函数重载的另一种替代方法是使用静态工厂。假设您希望能够从普通参数、包含相同参数的哈希甚至 JSON 字符串实例化一个对象:

    class Thing {
        constructor(a, b) {
            this.a = a;
            this.b = b;
        }
    
        static fromHash(hash) {
            return new this(hash.a, hash.b);
        }
    
        static fromJson(string) {
            return this.fromHash(JSON.parse(string));
        }
    }
    
    let thing = new Thing(1, 2);
    // ...
    thing = Thing.fromHash({a: 1, b: 2});
    // ...
    thing = Thing.fromJson('{"a": 1, "b": 2}');
    

    【讨论】:

    • 不错的一个。 . . .
    • 在这里使用new Thing()而不是new this()不是更好吗,因为那样会更安全(this可以根据静态函数中的内容改变含义)并且更清晰?
    • @Andrew 我不同意,在这里使用this 会使代码与其所属的类无关,更便携且更少冗余,因为该类在其定义中仅命名一次。如果类被扩展,它也应该避免问题。
    • 我想这真的取决于你如何使用它。您的示例中的静态构造函数相当通用,而我使用它的方式是针对该类的。我不确定我是否考虑让代码与上级类无关,在这种情况下我只会使用重构。使用this 可能需要较少的更改,但使用类名也会迫使开发人员注意到类名正在那里使用,如果他们尝试更改内容,这没关系,因为它都包含在类的定义中。
    • @Andrew 我想是的,我可以看到这两种方法的优缺点。
    【解决方案5】:

    这是一个基于 arity(参数数量)的重载技巧。这个想法是从许多具有不同属性的函数中创建一个函数(通过查看fn.length 来确定)。

    function overloaded(...inputs) {
      var fns = [];
    
      inputs.forEach(f => fns[f.length] = f);
    
      return function() {
        return fns[arguments.length].apply(this, arguments);
      };
    }
    
    var F = overloaded(
      function(a)    { console.log("function with one argument"); },
      function(a, b) { console.log("function with two arguments"); }
    );
    
    F(1);
    F(2, 3);
    

    当然,这需要大量的防弹和清理工作,但你明白了。但是,我认为将其应用于 ES6 类构造函数不会有太大的运气,因为它们是不同颜色的马。

    【讨论】:

    • 不是很推荐。但是出于好奇,为什么会说它很慢,以及如何定义“惯用”?
    • @BardiHarborow 感谢您的回复。我已将答案修改为“hack”而不是“general solution”。
    • 多年来我一直在使用类似的东西,没有问题。虽然它增加了调用的税,但它改善了读取。查看许多 jQuery 源函数,您会发现前 5-20 行根据调用的预期重载重新排列 args —— 速度快但读起来很糟糕。
    【解决方案6】:

    你可以使用静态方法,看my answer to same question

    class MyClass {
        constructor(a,b,c,d){
            this.a = a
            this.b = b
            this.c = c
            this.d = d
        }
        static BAndCInstance(b,c){
            return new MyClass(null,b,c)
        }
    }
    
    //a Instance that has b and c params
    MyClass.BAndCInstance(b,c)
    

    【讨论】:

      【解决方案7】:

      使用 object.assigne 与 this 的参数

      This={...this,...arguments}
      

      【讨论】:

        【解决方案8】:

        这不是我想要的重载,但这是我如何通过创建具有一些不同初始化行为的 obj1 来伪造我的方式的基本版本。我意识到我可以扩展上述论点,但我已经有一组令人讨厌的论点和相对不同的数据源需要解构,这会真正扭曲我的目标;这只是让我的情况更干净......

        class obj1{
          constructor(v1, v2){
            this.a = v1;
            this.b = v2;
          }
        }
        
        class obj1Alt{
          constructor(v1, v2){
            return new obj1(v1*2,v2*2);
          }
        }
        
        new obj1(2,4) // returns an obj1
        new obj1Alt(2,4) // also returns an obj1
        

        免责声明:我已经编程了很长时间,但是我对 JS 还很陌生;可能不是最佳做法。

        【讨论】:

          猜你喜欢
          • 2011-11-22
          • 1970-01-01
          • 2010-09-07
          • 1970-01-01
          • 2018-06-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多