Более короткая инициализация класса в ECMAScript 6

Каждый раз, когда я создаю некоторый класс, мне нужно выполнить ту же процедуру:

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

Есть ли способ сделать его более элегантным и короче? Я использую Babel, поэтому некоторые экспериментальные функции ES7 разрешены. Может быть, декораторы могут помочь?

Ответ 1

Вы можете использовать Object.assign:

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

Это функция ES2015 (или ES6), которая назначает собственные перечислимые свойства одного или нескольких исходных объектов целевому объекту.

Конечно, вам нужно написать имена arg дважды, но, по крайней мере, это намного короче, и если вы установите это как свою идиому, она хорошо справится с этим, когда у вас есть аргументы, которые вы хотите на экземпляре, и другие, t, например:

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

Ответ 2

К сожалению, все, что вы можете сделать, это простые вещи, такие как Object.assign, но если вы пытаетесь удалить избыточность ввода всех параметров дважды (один раз в сигнатуре конструктора и один раз в присваивании), вы не можете сделать.

Тем не менее, вы можете сделать такой взлом. Хотя я не уверен, что усилия и скромность обфускации, которая приходит с ней, того стоит.

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.

Ответ 3

Мы могли бы создать статический метод в каждом классе, который принимает объект 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 есть, и мы также имеем следующие преимущества:

  • Вы только записываете имена параметров один раз.
  • Вы защищены от минимизации.

В приведенном выше коде содержатся дополнительные аргументы (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
  }