ES6: вызов класса класса без нового ключевого слова

Учитывая простой класс

class Foo {
  constructor(x) {
    if (!(this instanceof Foo)) return new Foo(x);
    this.x = x;
  }
  hello() {
    return `hello ${this.x}`;
  }
}

Можно ли вызвать конструктор класса без ключевого слова new?

Использование должно позволить

(new Foo("world")).hello(); // "hello world"

или

Foo("world").hello();       // "hello world"

Но последнее не выполняется с

Cannot call a class as a function

Ответ 1

В классах есть "тело класса", которое является конструктором.
Если вы используете внутреннюю функцию constructor(), эта функция будет тем же самым телом класса и будет тем, что вызывается при вызове класса, поэтому класс всегда является конструктором.

Конструкторы требуют использования оператора new для создания нового экземпляра, так как при вызове класса без оператора new возникает ошибка, так как требуется, чтобы конструктор класса создавал новый экземпляр.

Сообщение об ошибке также довольно специфично и правильно

TypeError: конструкторы классов не могут быть вызваны без 'new'

Вы могли бы:

  • либо используйте регулярную функцию вместо класса 1.
  • Всегда вызывайте класс с помощью new.
  • Вызов класса внутри регулярной функции обхода, всегда используя new, таким образом вы получаете преимущества классов, но функцию обертывания все равно можно вызвать с помощью оператора new 2.

1)

function Foo(x) {
    if (!(this instanceof Foo)) return new Foo(x);
    this.x = x;
    this.hello = function() {
        return this.x;
    }
}

2)

class Foo {
    constructor(x) {
        this.x = x;
    }
    hello() {
        return `hello ${this.x}`;
    }
}

var _old = Foo;
Foo = function(...args) { return new _old(...args) };

Ответ 2

Как указывали другие, спецификация ES2015 строго указывает, что такой вызов должен вызывать TypeError, но в то же время он предоставляет функцию, которая может быть использована для достижения именно желаемого результата, а именно Прокси.

Прокси позволяют нам виртуализировать над понятием объекта. Например, они могут использоваться для изменения какого-либо поведения конкретного объекта, не затрагивая ничего другого.

В вашем конкретном случае использования class Foo есть Function object, который можно вызвать - это обычно означает, что тело этой функции будет выполнено. Но это можно изменить с помощью Proxy:

const _Foo = new Proxy(Foo, {
  // target = Foo
  apply (target, thisArg, argumentsList) {
    return new target(...argumentsList);
  }
});

_Foo("world").hello(); 
const f = _Foo("world");
f instanceof Foo; // true
f instanceof _Foo; // true

(Обратите внимание, что _Foo теперь является классом, который вы хотите открыть, поэтому идентификаторы, вероятно, должны быть наоборот)

Если запустить браузер, поддерживающий Proxies, вызов _Foo(...) теперь будет выполнять функцию apply trap вместо конструктора orignal.

В то же время этот "новый" _Foo класс неотличим от оригинала Foo (кроме возможности назвать его нормальной функцией). Аналогично, нет разницы, по которой вы можете указать объект, созданный с помощью Foo и _Foo.

Самый большой недостаток этого заключается в том, что он не может быть переполнен или заполнен, но все же его жизнеспособное решение для класса Scala -like применимо в JS в будущем.

Ответ 3

Вот образец, с которым я столкнулся, действительно помогает мне. Он не использует class, но не требует использования new. Win/Win.

const Foo = x => ({
  x,
  hello: () => `hello ${x}`,
  increment: () => Foo(x + 1),
  add: ({x: y}) => Foo(x + y)
})

console.log(Foo(1).x)                   // 1
console.log(Foo(1).hello())             // hello 1
console.log(Foo(1).increment().hello()) // hello 2
console.log(Foo(1).add(Foo(2)).hello()) // hello 3

Ответ 4

Нет, это невозможно. Конструкторы, созданные с помощью ключевого слова class, могут быть построены только с помощью new, если они [[call]] ed без них throw a TypeError 1 (и нет даже способа обнаружить это извне).
1: Я не уверен, что транспилеры получают это право

Вы можете использовать обычную функцию в качестве обходного пути:

class Foo {
  constructor(x) {
    this.x = x;
  }
  hello() {
    return `hello ${this.x}`;
  }
}
{
  const _Foo = Foo;
  Foo = function(...args) {
    return new _Foo(...args);
  };
  Foo.prototype = _Foo.prototype;
}

Отказ от ответственности: instanceof и расширение Foo.prototype работают как обычно, Foo.length не работает, .constructor и статические методы не могут быть исправлены добавлением Foo.prototype.constructor = Foo; и Object.setPrototypeOf(Foo, _Foo), если это необходимо.суб >

Для подкласса Foo (not _Foo) с class Bar extends Foo … вы должны использовать return Reflect.construct(_Foo, args, new.target) вместо вызова new _Foo. Подкласс в стиле ES5 (с Foo.call(this, …)) невозможен.

Ответ 5

я только что сделал этот модуль npm для вас;)

https://www.npmjs.com/package/classy-decorator

import classy from "classy-decorator";

@classy()
class IamClassy {
    constructor() {
        console.log("IamClassy Instance!");
    }
}

console.log(new IamClassy() instanceof IamClassy);  // true 

console.log(IamClassy() instanceof IamClassy);  // true 

Ответ 6

class MyClass {

  constructor(param) {
     // ...
  }

  static create(param) {
    return new MyClass(param);
  }

  doSomething() {
    // ...
  }

}

MyClass.create('Hello World').doSomething();

Это то, что вы хотите?

Если вам нужна логика при создании нового экземпляра MyClass, было бы полезно реализовать "CreationStrategy", чтобы опровергнуть логику:

class MyClassCreationStrategy {

  static create(param) {
    let instance = new MyClass();
    if (!param) {
      // eg. handle empty param
    }

    instance.setParam(param);
    return instance;
  }

}

class DefaultCreationStrategy {

  static create(classConstruct) { 
    return new classConstruct(); 
  }

}

MyClassCreationStrategy.create(param).doSomething();
DefaultCreationStrategy.create(MyClass).doSomething();

Ответ 7

Выкопал этот в проекте

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

Так что я думаю, что это невозможно с классами.

Ответ 8

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

В итоге у меня появился небольшой, но полезный шаблон, разрезающий конструктор в другую функцию. Период.

 class Foo {
  constructor() {
    //as i will not be able to call the constructor, just move everything to initialize
    this.initialize.apply(this, arguments)
  }

  initialize() {
    this.stuff = {};
    //whatever you want
  }
 }

  function Bar () {
    Foo.prototype.initialize.call(this); 
  } 
  Bar.prototype.stuff = function() {}

Ответ 9

Здесь, где вы можете использовать "конструктор безопасности области", Соблюдайте этот код:

function Student(name) {
  if(this instanceof Student) {
    this.name = name;
  } else {
    return new Student(name);
  }
}

Теперь вы можете создать объект Student без использования нового, как показано ниже:

var stud1 = Student('Kia');

Ответ 10

Хорошо, у меня есть еще один ответ здесь, и я думаю, что это довольно инновационный подход.

В принципе, проблема с выполнением чего-то похожего на ответ Наомика заключается в том, что вы создаете функции каждый раз, когда вы объединяете методы вместе.

EDIT: это решение имеет ту же проблему, однако этот ответ остается в образовательных целях.

Итак, здесь я предлагаю просто привязать новые значения к вашим методам - ​​которые в основном являются просто независимыми функциями. Это дает дополнительное преимущество - возможность импортировать функции из разных модулей в новый объект.

Ладно, вот и все.

const assoc = (prop, value, obj) => 
  Object.assign({},obj,{[prop]: value})

const reducer = ( $values, accumulate, [key,val] ) => assoc( key, val.bind( undefined,...$values ), accumulate )

const bindValuesToMethods = ( $methods, ...$values ) => 
  Object.entries( $methods ).reduce( reducer.bind( undefined, ...$values), {} )

const prepareInstance = (instanceMethods, staticMethods = ({}) ) => Object.assign(
  bindValuesToMethods.bind( undefined, instanceMethods ),
  staticMethods
)

// Let make our class-like function

const RightInstanceMethods = ({
  chain: (x,f) => f(x),
  map: (x,f) => Right(f(x)),
  fold: (x,l,r) => r(x),
  inspect: (x) => `Right(${x})`
})

const RightStaticMethods = ({
  of: x => Right(x)
})

const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)

Теперь вы можете сделать

Right(4)
  .map(x=>x+1)
  .map(x=>x*2)
  .inspect()

Вы также можете сделать

Right.of(4)
  .map(x=>x+1)
  .map(x=>x*2)
  .inspect()

У вас также есть дополнительное преимущество, заключающееся в возможности экспорта из модулей как таковых

export const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)

Пока вы не получаете ClassInstance.constructor, у вас есть FunctorInstance.name (обратите внимание, вам может понадобиться polyfill Function.name и/или не использовать функцию стрелки для экспорта для совместимости браузера с Function.name)

export function Right(...args){
  return prepareInstance(RightInstanceMethods,RightStaticMethods)(...args)
}

PS - Приветствуются новые предложения имени для prepareInstance, см. Gist.

https://gist.github.com/babakness/56da19ba85e0eaa43ae5577bc0064456

Ответ 11

У меня были проблемы с расширением классов, преобразованных с помощью функции преобразования, упомянутой в некоторых других ответах. Проблема заключается в том, что node (начиная с версии v.9.4.0) не поддерживает оператор спреда аргумента ((...args) =>).

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

// function that calls `new` for you on class constructors, simply call
// YourClass = bindNew(YourClass)
function bindNew(Class) {
  function _Class() {
    for (
      var len = arguments.length, rest = Array(len), key = 0;
      key < len;
      key++
    ) {
      rest[key] = arguments[key];
    }

    return new (Function.prototype.bind.apply(Class, [null].concat(rest)))();
  }
  _Class.prototype = Class.prototype;
  return _Class;
}

Использование:

class X {}
X = bindNew(X);

// or

const Y = bindNew(class Y {});

const x = new X();
const x2 = X(); // woohoo

x instanceof X; // true
x2 instanceof X; // true

class Z extends X {} // works too

В качестве бонуса TypeScript (с выходом "es5" ) кажется прекрасным со старым трюком instanceof (ну, он не будет проверяться typecheck, если используется без new, но он работает так или иначе):

class X {
  constructor() {
    if (!(this instanceof X)) {
      return new X();
    }
  }
}

потому что он скомпилирует его:

var X = /** @class */ (function () {
    function X() {
        if (!(this instanceof X)) {
            return new X();
        }
    }
    return X;
}());

Ответ 12

Вызов конструктора класса без ключевого слова new невозможен.

Сообщение об ошибке довольно специфично.

Смотрите сообщение в блоге 2ality и spec:

However, you can only invoke a class via new, not via a function call (Sect. 9.2.2 in the spec):

    > Point()
    TypeError: Classes can’t be function-called

Ответ 13

Я добавляю это как продолжение комментария наомика и использую метод, иллюстрированный Тимом и Берги. Я также собираюсь предложить функцию of для использования в качестве общего случая.

Сделать это функциональным способом И использовать эффективность прототипов (не воссоздавать весь метод каждый раз, когда создается новый экземпляр), можно использовать этот шаблон

const Foo = function(x){ this._value = x ... }
Foo.of = function(x){ return new Foo(x) }
Foo.prototype = {
  increment(){ return Foo.of(this._value + 1) },
  ...
}

Обратите внимание, что это согласуется с fantasy-land спецификациями JS

https://github.com/fantasyland/fantasy-land#of-method

Я лично считаю, что чище использовать синтаксис класса ES6

class Foo {
  static of(x) { new Foo(x)}
  constructor(x) { this._value = x }
  increment() { Foo.of(this._value+1) }
}

Теперь можно обернуть это в закрытие как таковое

class Foo {
  static of(x) { new _Foo(x)}
  constructor(x) { this._value = x }
  increment() { Foo.of(this._value+1) }
}


function FooOf (x) {

    return Foo.of(x)

}

Или переименуйте FooOf и Foo по желанию, т.е. класс может быть FooClass и функция просто Foo и т.д.

Это лучше, чем разместить класс в функции, потому что создание новых экземпляров не мешает нам создавать и новые классы.

Еще один способ - создать функцию of

const of = (classObj,...args) => (
  classObj.of 
    ? classObj.of(value) 
    : new classObj(args)
)

И затем сделайте что-то вроде of(Foo,5).increment()

Ответ 14

Вы не можете использовать класс без нового конструктора, в моем случае я не хотел использовать new конструктор всякий раз, когда хотел использовать свой класс, так что вы можете сделать так, чтобы обернуть ваш класс следующим образом (в моем случае это библиотека Dates utils):

enter image description here

Ответ 15

Это может быть немного надуманным, но он работает

function Foo(x){
"use strict"

    class Bar {
        constructor(x) {
            if (!(this instanceof Bar)) return new Bar(x);
            this.x = x;
        }

        hello() {
            return `hello ${this.x}`;
        }
    }

    return new Bar(x)
}

Foo("world").hello()