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
21 ответ
Классы имеют "тело класса", которое является конструктором.
Если вы используете внутренний 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) };
Как уже отмечалось, спецификация 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
теперь класс, который вы хотите показать, поэтому идентификаторы должны быть наоборот
Если запустить браузер, поддерживающий прокси, вызов _Foo(...)
теперь выполню apply
функция ловушки вместо оригинального конструктора.
В то же время этот "новый" _Foo
класс неотличим от оригинала Foo
(кроме возможности вызывать его как обычную функцию). Точно так же нет разницы, по которой вы можете сказать объект, созданный с Foo
а также _Foo
,
Самым большим недостатком этого является то, что он не может быть перенесен или заполнен, но все же его жизнеспособное решение для использования подобного Scala класса будет применяться в JS в будущем.
Вот образец, с которым я столкнулся, который действительно помогает мне. Это не использует 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
Я только что сделал этот модуль 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
Нет, это невозможно. Конструкторы, созданные с использованием class
Ключевое слово может быть построено только с new
, если они [[call]]ed без них всегда throw
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
(не _Foo
) с class Bar extends Foo …
, вы должны использовать return Reflect.construct(_Foo, args, new.target)
вместо new _Foo
вызов. Подклассы в стиле ES5 (с Foo.call(this, …)
) это невозможно.
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();
Вот где вы можете использовать "безопасный конструктор области видимости". Обратите внимание на этот код:
function Student(name) {
if(this instanceof Student) {
this.name = name;
} else {
return new Student(name);
}
}
Теперь вы можете создать объект Student без использования new следующим образом:
var stud1 = Student('Kia');
Выкопал этот в проекте
Конструкторы, определенные с использованием синтаксиса определения класса, при вызове функций
Так что я думаю, что это невозможно с классами.
Вызов конструктора класса вручную может быть полезен при рефакторинге кода (наличие частей кода в 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() {}
Хорошо, у меня есть другой ответ, и я думаю, что он довольно инновационный.
По сути, проблема с выполнением чего-то похожего на ответ Наомика заключается в том, что вы создаете функции каждый раз, когда объединяете методы в цепочку.
РЕДАКТИРОВАТЬ: Это решение имеет ту же проблему, однако этот ответ оставлен в образовательных целях.
Итак, здесь я предлагаю способ просто привязать новые значения к вашим методам, которые в основном являются просто независимыми функциями. Это дает дополнительное преимущество возможности импортировать функции из разных модулей во вновь создаваемый объект.
Хорошо, вот так.
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
(обратите внимание, вам может понадобиться заполнить Function.name
и / или не использовать функцию стрелки для экспорта для совместимости браузера с Function.name
цели)
export function Right(...args){
return prepareInstance(RightInstanceMethods,RightStaticMethods)(...args)
}
PS - приветствуются новые предложения по имени для prepareInstance, см. Gist.
https://gist.github.com/babakness/56da19ba85e0eaa43ae5577bc0064456
У меня были проблемы с расширением классов, преобразованных с помощью функции преобразования, упомянутой в некоторых других ответах. Кажется, проблема в том, что узел (начиная с версии 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
трюк (ну, это не будет проверка типа, если используется без 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;
}());
Повторное рабочее «однострочное» решение для ES6: объяснение
Ответ, опубликованный Берги выше, в основном правильный.
TLDR; пропустить до конца для однострочного решения
Ответ Берги может показаться неясным при чтении. Итак, вот более расширенный пример кода, который иллюстрирует ДВЕ новые функции ES6 для достижения желаемых целей.
Вместе они позволяют одной функции (см. ниже) обеспечивать двойную роль фабрики и новой функции fn ; который создает inst, производный от .
Конструктор использует обработку для вызова конструктора с аргументами инициализации. В наших последних примерах № 3–4, созданных с помощью.
The
A
конструктор демонстрирует семантику псевдо-var для обнаруженияnew
был фактически вызван с помощью .
Во-первых, мы будем использовать псевдо-переменную ES6, которая дает нам
RHS
из
new RHS()
выражение.
Технически мы могли бы получить
new.target
в качествеthis?.__proto__?.constructor
; они эквивалентны.
Во-вторых, мы будем использовать ES6. Что крайне важно для обхода ограничений вызова конструктора класса ES6; если мы связаны и полны решимости не использовать
new RHS(...)
.
Протестируйте следующее и убедитесь сами в его результатах (также представленных в пунктах 1–4 ниже).
class A {
constructor(...a) {
const descendentType = new.target;
console.log(`A's constructor seeing 'new' invoked on ${descendentType?.name} with args: %o`,a);
}
}
class B extends A {
constructor(...a) {
super(...a);
}
}
// C is our DUAL mode Factory
function C(...a) {
console.log(`C's new.target => ${new.target?.name}`);
const inst = new.target ? Reflect.construct(B, a) : new B(...a);
console.log(`C has constructed a ${inst.__proto__.constructor.name} inst`);
return inst;
}
Затем мы можем вызвать его следующими способами:
-
new A('NEW-A()')
- output => "Конструктор A видит, что 'new' вызывается для A с аргументами: ['NEW-A()']"
-
new B('NEW-B()')
- output => "Конструктор A видит, что 'new' вызывается для B с аргументами: ['NEW-B()']"
-
new C('NEW-C()')
- вывод => "C's new.target => C"
- output => "Конструктор A видит, что 'new' вызывается для B с аргументами: ['NEW-C()']"
- вывод => "C построил экземпляр B"
-
C('PLAIN-C()')
- output => "C's new.target => undefined"
- output => "Конструктор A видит, что 'new' вызывается для B с аргументами: ['PLAIN-C()']"
- вывод => "C построил экземпляр B"
Где № 3 и № 4 достигают изначально желаемых целей.
Упрощенный ` C` выглядит так:
function C(...a) {return Reflect.construct(B, a);}
ИЛИ - если 3-й аргумент
Reflect.construct
не используется для инициализации.
function C(...a) {return new B(...a);}
Осторожно: это должна быть функция , а не
class
чтобы это было разрешено и работало, возвращая альтернативныйthis
наnew C()
призыв и др.
Также, чтобы обойти правила строгого режима для
arguments.callee
требует использования замыкания (smalltalk-block. Иллюстрировано ниже:
class B extends A {
// embedding within a class and generically referencing it requires =>
static C = (() => {
const $class = this; return function(...a) {
return Reflect.construct($class, a);}})();
// Read more on `Reflect.construct` 3rd argument to see more capabilities
// for why it does MORE than just `new $class(...a)` would do.
}
exports.C = B.C;
⛐⚠️⛐ Вы могли бы делать ужасные вещи, например,
__proto__
на полученномinst
и изменить егоconstructor
а такжеname
. Что заставило бы его выглядеть и чувствовать себя как настоящий подклассC
изB
в зависимости от того, как далеко вы хотите зайти, чтобы манипулировать объектной моделью. Тонкости изобилуют тем, что происходит сgetters/setters
,super
а также#
приват. Но для большей части этого вы можете ОСТАВАТЬСЯ ЧИСТЫМ ES6 и поумнеть, используяextends
и предоставление сплющенного дерева примесей суперкласса шаблона ; который я много делаю для поддержки крошечных, но полных реактивных частей пользовательских элементов µhtml и связанных с ними моделей приложений PWA, а также гибкого динамического своевременного связывания версии кода с серверных серверов EdgeS ESS. Как в ...const M = $class => class extends $class {...}
.
Мои мотивы...
Я опубликовал это, чтобы помочь объяснить семантику и рабочее решение ES6, которое я использую для поддержки подклассов.Promise
предоставлять
FutureValue
с улучшенными
возможностями обработки рабочих процессов в моей
github
efekt library
(библиотека EdgeS Front End Kit).
Как указано вами и другими
Foo("world").hello();
завершается с ошибкой, поскольку это ошибка в соответствии с правилами синтаксиса ES6.
Другие отметили, что
(new Foo("world")).hello();
работает, но неуклюже, потому что
- Ему нужны "новые" И
- Нужны дополнительные скобки.
Я согласен, это неуклюже. Поэтому я часто использую это решение:
В своем классе Foo создайте статический метод с именем 'new':
static new (...args) { return new this (...args); }
Используйте это так:
Foo.new("world").hello();
Таким образом я скрываю "неуклюжесть" внутри статического метода new().
Обратите внимание, что этот метод new() является универсальным, он будет работать так же, как и при наследовании подклассов. Если вам нужно настроить его в подклассе, вы можете сначала вызвать:
super.new(...args)
а затем добавьте все, что вам нужно, в метод в подклассе, прежде чем возвращать его результат.
В 2022 году, начиная с ES6, вы можете сделать это с помощьюstatic
метод, который можно вызвать перед созданием экземпляра класса, чтобы создать экземпляр класса.
Таким образом, код должен выглядеть примерно так:
class Foo {
constructor(x) {
this.x = x;
}
//static class
static Init(x) {
return new Foo(x)
}
sayHello() {
return `hello ${this.x}`;
}
}
//so if i call
Foo.Init('world').sayHello();
//it prints: hello world
Но если вы делаете все это, чтобыchain of method
вы также можете посмотреть на следующую конструкцию:
function MyName(name) {
if (this instanceof MyName) {
this.name = name,
this.prepend = function(n) {
this.name = `${n} ${this.name}`;
return this;
}
,
this.append = function(n) {
this.name = `${this.name} ${n} `;
return this;
}
,
this.show = function() {
return this.name;
}
} else {
return new MyName(name);
}
}
//Call
MyName('vinod').prepend('dev').append('hacks').show();
//prints: dev vinod hacks
Метод выше возвращаетthis
в конце каждого метода, который делает доступными объект, свойства и метод.
Хорошая часть заключается в том, что эти методы можно использовать снова и снова, чтобы создать предложение как
MyName('vinod').prepend('dev').append('hacks')
.prepend("the").append('javascript').append('for Stackoverflow').show();
Я использовал его какstringBuilder
или генерироватьxml
динамически.
Я написал небольшую вспомогательную функцию, которая решает эту проблему. Он эффективно преобразует класс ES6 в более старую функцию конструктора ES5, которая не подчиняется тому же набору правил. Таким образом вы можете создавать конструкторы, которые не нуждаются в
new
. Вы также можете перегружать конструкторы аналогично встроенному
Number
,
String
и т.п.
function callableConstructor(c, f) {
function ret(...args) {
if(new.target) {
return new c(...args)
}
return f(...args)
}
ret.prototype = c.prototype
ret.prototype.constructor = ret
return ret
}
Проверьте это ниже:
Все еще ищу интересные способы использования
instanceof
не полагаясь на
new
или же
class
ключевые слова. В этом примере программы мы вычисляем 100000-е число Фибоначчи менее чем за одну секунду. Результат составляет более 20000 цифр -
const fib = x =>
Loop // <- no `new`
( (n, a, b) =>
n <= 0n
? String(a) // <- no `new`
: Recur(n - 1n, b, a + b) // <- no `new`
, BigInt(x) // <- no `new`
, 0n
, 1n
)
function Loop (f, ...init)
{ let r = f(...init)
while (r instanceof Recur) // <- instanceof works
r = f(...r)
return r
}
function Recur (...v)
{ return Object.create // <- not a class, but works
( Recur.prototype // <- set prototype
, { constructor: { value: Recur } // <- set constructor
, [Symbol.iterator]: { value: _ => v.values() } // <- whatever you want
}
)
}
document.body.textContent = fib(100000)
body { overflow-wrap: anywhere; }
Не знаю, почему я не подумал об этом раньше -
function atom (T, v)
{ return Object.assign
( Object.create
( T.prototype
, { constructor: { value: T } }
)
, v
)
}
function pair (car, cdr)
{ return atom(pair, { car, cdr }) }
const p =
pair(1, 2)
console.log(p)
console.log(p instanceof pair)
Вывод -
{
"car": 1,
"cdr": 2
}
true
Я добавляю это в качестве дополнения к комментарию Наомика и использую метод, проиллюстрированный Тимом и Берги. Я также собираюсь предложить 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
Я лично чувствую, что лучше использовать синтаксис класса 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()
Я пришел к этому вопросу, потому что я столкнулся с
Я по-прежнему хотел использовать синтаксис класса, потому что он мне нравится, но я согласен с тем, что обычный класс с ключевым словом new для чего-то, что не создает объект, может сбивать с толку.
Решение для меня было простым. Определите неэкспортированный класс в модуле и экспортируйте функцию, которая его реализует.
class SideEffects {
constructor() {
}
// ...
}
export function addSideEffects() {
// eslint-disable-next-line no-new
new SideEffects();
}
Да, мы все еще используем
Вызов конструктора класса без new
Ключевое слово не возможно.
Сообщение об ошибке довольно специфично.
Смотрите сообщение в блоге о 2ality и спецификации:
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
Это может быть немного надуманным, но это работает
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()
Вы не можете использовать класс без нового конструктора, в моем случае я не хотел использовать new
конструктор в любое время, когда я хотел использовать свой класс, поэтому вы можете обернуть свой класс следующим образом (в моем случае это библиотека Dates utils):