为什么JavaScript ES6不支持多构造函数类?

56

我希望像下面这样编写我的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}

3
“为什么不”通常是一个没有成果的问题。因为,嗯,它就是不。许多语言都不支持,ES6远远不是唯一的一种。 - deceze
你不需要一个过载的构造函数。只需使用默认值声明autoLoad即可。 - user663031
3
可能是 JavaScript 中的方法重载 的重复问题(在 class 中也一样,不同之处在于多个定义会导致语法错误)。 - Bergi
几乎相同的问题:为什么JavaScript对象不允许您为一个键指定多个值? - Patrick Roberts
@deceze 错误。通常像这样提出问题可能会给你以下结果之一或两者兼有:A. 了解为什么语言不能/不应该支持你认为它应该支持的内容,或B. 得到好的替代方案来支持你想要做的事情。这里有一些很好的例子:https://dev59.com/pFwY5IYBdhLWcg3wcHXZ#32626901 https://dev59.com/pFwY5IYBdhLWcg3wcHXZ#41051984 https://dev59.com/pFwY5IYBdhLWcg3wcHXZ#48287734 - Andrew
显示剩余2条评论
8个回答

32

我想按照下面的方式编写我的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 示例

当然(按照您更新后的示例),您可以采取不关心参数数量的方法,而是关心是否传递了每个单独的值,这种情况下您可以像这样做:

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;
    }
}

8
为什么我们如此坚决地拒绝使用参数默认值? - user663031
4
“我们”并不是我在回答如何模拟函数重载的问题。我可能在原始代码的其他方面错过了一些优化,其中参数默认值是我认为可能有多个选择之一。 - CodingIntrigue
通常不建议使用OR双管符“||”来设置默认值,特别是在es6支持它的情况下。http://www.codereadability.com/javascript-default-parameters-with-or-operator/amp/ - GFoley83

21
您希望的是构造函数重载。这和更一般化的函数重载情况在ECMAScript中都没有得到支持。
ECMAScript无法像更严格的语言那样处理缺少的参数。缺少参数的值将保留为undefined而不是引发错误。在这种范式下,很难/不可能检测到您要瞄准的哪个重载函数。
通用解决方案是拥有一个功能强大的函数,并让它处理您需要的所有参数组合。对于最初的示例,您可以像这样测试keyvalue的存在:
class Option {
  constructor(key, value, autoLoad = false) {
    if (typeof key !== 'undefined') {
      this[key] = value;
    }
    this.autoLoad = autoLoad;
  }
}

3
如果他在使用 class,那么他在使用 ES6。ES6有参数默认值功能。请使用它们。 - user663031
4
默认值在可变参数函数中效果不佳。 - Bergi

11

从您的示例代码推测,您只需要使用参数的默认值:

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}');

1
不错。. . . - Andrew
1
这里使用new Thing()会更好,而不是new this(),因为这样更安全(this的含义可能会根据静态函数中的内容而改变),也更清晰明了。 - Andrew
@Andrew 我不得不反对,使用 this 可以使代码与其所属的类无关,更具可移植性和更少的冗余,因为类只在其定义中命名一次。这也应该避免在类被扩展时出现问题。 - Diogo Eichert
1
我想这真的取决于你如何使用它。在你的例子中,静态构造函数相当通用,而我使用的方式则相当特定于类。我不确定我是否认为使代码对类具有普适性更优越,我只会在那种情况下使用重构。使用this可能需要更少的更改,但是使用类名也迫使开发人员注意到类名正在被使用,如果他们尝试更改事物,这是可以的,因为所有内容都包含在类的定义中。 - Andrew
1
@Andrew 我想是的,我可以看到两种方法都有优缺点。 - Diogo Eichert

10

另一个选择是允许您的构造函数接受绑定到类属性的对象:

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 只能是布尔型等)。


6

这是一个基于参数个数重载的技巧。其思想是根据函数的不同参数个数(通过查看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类构造函数中,因为它们是完全不同的事情。


不是真的推荐这样做。但出于好奇,你为什么会说它很慢,以及你如何定义“惯用语”? - user663031
2
@BardiHarborow 谢谢您的回复。我已经修改了我的答案,用“hack”代替了“通用解决方案”。 - user663031
我多年来一直在使用类似的东西,没有出现任何问题。虽然它增加了调用的税费,但它提高了可读性。看看许多jQuery源函数,你会发现对于许多函数,前5-20行是根据调用的预期重载重新排列参数--速度很快,但阅读起来很糟糕。 - Mario

2

你可以使用静态方法,查看我对同样问题的回答

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)

0

这不是我想要的重载,但这是我通过创建一个具有一些不同初始化行为的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来说还比较新手;可能不是最佳实践。


0

使用 Object.assign 与带有 this 的参数

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接