2015-06-07 2 views
48

Учитывая простой класс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 
+0

Сообщите мне, если вы нашли способ, хотя! –

+1

Имейте в виду, что обход - это просто определить фабричную функцию (с немного другим именем), которая просто возвращает новый Foo (arg); '. – jfriend00

+0

Да, я рассмотрел это, но тогда есть асимметрия между именем конструктора и именем класса: { – naomik

ответ

27

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

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

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

TypeError: Class constructors cannot be invoked without 'new'

Вы могли;

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

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) }; 
+0

В следующих версиях будут добавлены конструкторы вызовов: 'class Cat {call constructor() {new Cat()}}' – Maxmaxmaximus

+0

В настоящее время узел (v9.4.0), похоже, не поддерживает аргумент оператора спредов, и это вызывало меня вопросы. Я сделал версию, основанную на переданном выходе стильного декоратора, упомянутом в другом ответе. '' ' функции bindNew (класс) { функция _Class() { для ( вара Len = arguments.length, остальное = Array (LEN), ключ = 0; клавиши <длина; ключа ++ ) { rest [key] = arguments [key]; } return new (Function.prototype.bind.apply (класс, [null] .concat (rest)))(); } _Class.prototype = Class.prototype; return _Class; } '' ' – kasbah

+0

Добавлено выше [в качестве ответа] (https://stackoverflow.com/a/48326964/738768). – kasbah

3

вскопаны этот в the draft

Constructors defined using class definition syntax throw when called as functions

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

+0

Спасибо. Вы все правы, но я дал первый ответ. – naomik

0

Вызов конструктора классов без ключевого слова 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 
-1

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

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() 
+2

Я в замешательстве, почему у вас есть проверка 'instanceof', так как вы даже не подвергаете класс? Этот ответ действительно не затрагивает основную проблему. – loganfsmyth

+2

Это также не работает, потому что 'Foo ('world') instanceof Foo' возвращает' false'. – naomik

10

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

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

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 (не _Foo) с class Bar extends Foo …, вы должны использовать return Reflect.construct(_Foo, args, new.target) вместо new _Foo вызова. Подкласс в стиле ES5 (с Foo.call(this, …)) невозможен.

5
class MyClass { 

    constructor(param) { 
    // ... 
    } 

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

    doSomething() { 
    // ... 
    } 

} 

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

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

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

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(); 
+4

Whoa, JavaScript действительно будет выглядеть как Java сейчас: -/ – Bergi

+2

Подсказка: классы, которые имеют только статические члены, не должны быть 'class'es, а просто объектами. В случае только одного члена они вообще не должны быть. – Bergi

+0

Обычно я не определял статический метод 'create'. в этом примере я использую 'static', потому что создатель хочет создавать объекты без ключевого слова' new'. – Tim

19

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

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

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

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

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

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

Самым большим недостатком этого является то, что it cannot be transpilled or pollyfilled, но все же его жизнеспособное решение для применения класса Scala применимо в JS в будущем.

+0

Это только работающее решение. Все остальные ответы не работают при некоторых обстоятельствах. Удивительно, насколько неточная система рейтинга StackOverflow - это единственный правильный ответ в нижней части списка. – wandalen

+0

@wandalen - это явно не только * рабочий ответ, на самом деле правильный ответ на вопрос просто * «нет, это невозможно» *. Это * другой * ответ, который использует прокси вместо экземпляров, созданных с помощью 'new', и это простой способ справиться с этой проблемой. – adeneo

2

вызов конструктора класса вручную может быть полезными, когда рефакторинга кода (имеющие части кода в 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() {} 
12

Вот образец, с которым я столкнулся, что действительно помогает мне. Он не использует 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

Это заслуживает очков. Я действительно задаюсь вопросом, является ли добавление класса к JS улучшением. Это показывает, какой код JS должен выглядеть. Для людей, интересующихся, почему в этом нет никакого «этого», созданный объект просто использует «x», который был передан в «конструктор» (функция стрелки). Всякий раз, когда он нуждается в мутации, он возвращает * новый * объект. Объекты неизменяемы. –

+0

Интересно, будет ли он оптимизировать функции в прототипе, или если он создаст новые функции для каждого объекта. Может быть, с 'Object.freeze' он будет оптимизирован? – YoYoYonnY

+0

создаст новые функции – naomik

0

Вот где вы можете использовать области видимости безопасного конструктора ' соблюдать этот код:

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

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

var stud1 = Student('Kia'); 
3

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

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 
1

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

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

// 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 

В качестве бонуса, машинопись (с "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; 
}()); 
0

Я добавляю это как дополнение к комментарию наомика и используя метод, иллюстрированный Тимом и Берги. Я также собираюсь предложить функцию 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()

1

Alright У меня есть другой ответ здесь, и я думаю, что это один довольно инновационный.

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

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's 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

+0

Я думаю, что вижу проблему с исправлением, но я могу ошибаться. Каждый раз, когда мы применяем Right (например, 'Right (1)', 'Right (2)'), вызывается бит Object.entries ($ methods) .reduce'. Я думаю, вы намерены выполнить это сокращение только один раз. Это верно? – naomik

+0

Это довольно аккуратно. Это очень многое делает в очень немногих строках кода. – naomik

+0

@naomik Спасибо! Хм ... Вам все еще нужно связать новое значение (-и) в контейнере-функторе с методами возвращаемого функтора? Я просто оптимизировал код, поместив редуктор за пределы функции уменьшения, чтобы предотвратить повторное создание каждого вызова. – Babak

Смежные вопросы