ECMAScript 6中更短的类初始化

11

每次我创建一个类,都需要做同样无聊的流程:

class Something {
  constructor(param1, param2, param3, ...) {
    this.param1 = param1;
    this.param2 = param2;
    this.param3 = param3;
    ...
  }
}

有没有什么方法可以使它更加优雅和简洁?我使用Babel,所以一些ES7实验性功能是允许的。也许装饰器可以帮助吗?


2
需要使用类吗?你可以使用简写对象字面量将参数分配为属性,而无需重复它们的名称。 - Touffy
1
回答你的最后一个问题,装饰器在这里无法帮助,因为它们不能附加到“类构造函数”。 - CodingIntrigue
3个回答

10

您可以使用Object.assign

class Something {
  constructor(param1, param2, param3) {
    Object.assign(this, {param1, param2, param3});
  }
}

这是一个ES2015(又称ES6)功能,它将一个或多个源对象的可枚举属性分配给目标对象。

当然,需要将参数名称写两次,但至少它更短,如果您将其确定为自己的习惯用语,则处理实例上确实需要的参数和不需要的参数时会很好,例如:

class Something {
  constructor(param1, param2, param3) {
    Object.assign(this, {param1, param3});
    // ...do something with param2, since we're not keeping it as a property...
  }
}

示例:(Babel的REPL中的实时副本):

class Something {
  constructor(param1, param2, param3) {
    Object.assign(this, {param1, param2, param3});
  }
}
let s = new Something('a', 'b', 'c');
console.log(s.param1);
console.log(s.param2);
console.log(s.param3);

输出:

a
b
c

这很不错,但你仍在重复参数名称(一次在参数列表中,一次在分配调用中)。你可以克隆“arguments”,删除预定义属性,并将其传递给“assign”。 - Touffy
@Touffy:arguments 对于参数有命名属性吗?我的意思是,除了 012 之外还有其他名称吗?如果是这样的话,那对我来说是新的。 - T.J. Crowder
嗯,确实不行。我想你可以从函数的toString中轻松解析它们,但那样会很丑陋。 - Touffy
@Touffy:是的,这是一个有用的习惯用语,即使你只需要将其中一些映射到对象上,你仍然可以继续使用它。 - T.J. Crowder
我会将第一个参数视为属性映射,就像Chev的最后一个解决方案一样(当然,使用Object.assign而不是循环)。但是如果您不信任调用代码发送正确的名称,则您的解决方案更安全。 - Touffy

3

不幸的是,你所能做的只有一些简单的事情,例如 Object.assign。但是,如果你试图消除在构造函数签名和赋值中重复输入所有参数的冗余,那么你几乎没有什么可做的。

话虽如此,你可以尝试像这样的 hack。虽然我不确定这样做所需的努力和混淆程度是否值得。

var dependencies = ['param1', 'param2', 'param3'];

class Something {
  constructor(...params) {
    params.forEach((param, index) => this[dependencies[index]] = param);
  }
}

var something = new Something('foo', 'bar', 'baz');
// something.param1 === 'foo'

通过使用单个参数名称数组,然后在创建Something实例的属性时将同一数组用作参考,以此方式来进行操作。当您在Angular应用程序中尝试通过设置$inject属性来保留依赖项名称时,此模式非常有效。

Something.$inject = dependencies;

PS- 欢迎来到我认为我已经脱离的古典语言的冗余地狱,当我成为JS开发人员时 :P

老实说,除非你真的需要一个真正类的正式性,否则最好使用经典的对象字面量。

编辑:如果你想要文字简单和真正的类的正式性,你可以在构造函数中接受一个对象字面量。

class Something {
  constructor(params) {
    Object.keys(params).forEach((name) => this[name] = params[name]);
  }
}

var something = new Something({
  param1: 'foo',
  param2: 'bar',
  param3: 'baz'
});

但现在你已经将一个类转换成了一个���态类,可以用任意属性实例化,有点像对象字面量:P

通常我想要一个类是因为我想使对象形式化,并提供一致且严格可测试的API。


1
这段代码看起来不太美观,但是它可以给你函数参数的名称,这样你就可以在函数定义中直接写出它们,而不需要使用外部数组:arguments.callee.toString().match(/^function.*?\((.*?)\)/)[1].split(/,\s*/) } - Touffy
1
@Touffy 那对我不起作用。严格模式不允许我访问 arguments.callee,因此您可能需要一个进一步的解决方法来解决该问题,使其更加丑陋。 - CodingIntrigue

1
我们可以在每个类中创建一个静态方法,它接受`arguments`对象和名称数组,并返回一个可用于使用`Object.assign`分配给新实例的对象。
检查它使用Babel REPL
class Something {
  static buildArgs (ctx, args, paramNames) {
    let obj = {}
    Array.from(args).forEach(function (arg, i) {
      let name = paramNames[i] || i
      obj[name] = args[i]
    })
    Object.assign(ctx, obj)
  }

  constructor () {
    Something.buildArgs(this, arguments, [
      'param1',
      'param2'
    ]);
    console.log(this)
  }
}

new Something('one', 'two')

可以承认,添加一个buildArgs方法意味着这个解决方案并不更短,但是constructor的主体更短了,我们也有以下优点:

  1. 只需编写一次参数名称。
  2. 受到缩小保护。

上面的代码适应额外的参数(i >= paramNames.length),但是如果不希望出现这种行为,我们可以进行修改,以便仍然解析这些参数,但不将其分配给实例:

class Something {
  static buildArgs (ctx, args, paramNames) {
    let obj = {instance: {}, extra: {}}
    Array.from(args).forEach(function (arg, i) {
      let name = paramNames[i] || i
      if (name) {
          obj.instance[name] = args[i]
      } else {
          obj.extra[i] = args[i]
      }
    })
    Object.assign(ctx, obj)
  }

  constructor () {
    let args = Something.buildArgs(this, arguments, ['param1', 'param2']);
    // Do stuff with `args.extra`
  }
}

或者完全被忽略:
  static buildArgs (args, paramNames) {
    let obj = {}
    Array.from(args).forEach(function (arg, i) {
      let name = paramNames[i]
      if (name) obj[name] = args[i]
    })
    return obj
  }

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