javascript - ES6: construtor de classe de chamada sem nova palavra-chave




constructor ecmascript-6 (10)

Dada uma classe simples

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

É possível chamar o construtor de classe sem a new palavra-chave?

O uso deve permitir

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

Ou

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

Mas o último falha com

Cannot call a class as a function

Acabei de criar este módulo NPM para você;)

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 

Aqui é onde você pode usar um 'construtor seguro de escopo' Observe este código:

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

Agora você pode criar um objeto Student sem usar new da seguinte maneira:

var stud1 = Student('Kia');

As classes têm um "corpo de classe" que é um construtor .
Se você usar uma função constructor() interna constructor() , essa função também seria o mesmo corpo de classe e seria o que é chamado quando a classe é chamada, portanto, uma classe é sempre um construtor.

Os construtores requerem o uso do new operador para criar uma nova instância, assim, invocar uma classe sem o new operador resulta em um erro, pois é necessário que o construtor da classe crie uma nova instância.

A mensagem de erro também é bastante específica e correta

TypeError: Construtores de classe não podem ser chamados sem 'new'

Você poderia;

  • use uma função regular em vez de uma classe 1 .
  • Sempre chame a turma com new .
  • Chame a classe dentro de uma função regular de empacotamento, sempre usando new , para obter os benefícios das classes, mas a função de empacotamento ainda pode ser chamada com e sem o new operador 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) };

Chamar o construtor da classe sem a new palavra-chave não é possível.

A mensagem de erro é bastante específica.

Veja uma postagem no blog sobre 2ality e as 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 cant be function-called

Desenterrou este no rascunho

Construtores definidos usando a sintaxe de definição de classe lançada quando chamados como funções

Então acho que isso não é possível com as aulas.


Estou adicionando isso como acompanhamento de um comentário da naomik e utilizando o método ilustrado por Tim e Bergi. Também vou sugerir uma função para usar como um caso geral.

Para fazer isso de uma maneira funcional E utilizar a eficiência dos protótipos (não recriar todos os métodos cada vez que uma nova instância é criada), pode-se usar esse padrão

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

Observe que isso é consistente com as especificações JS do fantasy-land

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

Pessoalmente, acho que é mais fácil usar a sintaxe da classe ES6

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

Agora, pode-se encerrar isso como um fechamento

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)

}

Ou renomeie FooOf e Foo conforme desejado, ou seja, a classe pode ser FooClass e a função apenas Foo , etc.

É melhor do que colocar a classe na função, porque criar novas instâncias não nos sobrecarrega com a criação de novas classes também.

Ainda outra maneira é criar uma função

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

E então faça algo como of(Foo,5).increment()


Não, isso não é possível. Os construtores criados usando a palavra-chave class podem ser construídos apenas com new , se forem [[call]]ed sem sempre throw um TypeError 1 (e não há nem uma maneira de detectar isso de fora).
1: Não tenho certeza se os transpilers estão certos

Você pode usar uma função normal como solução alternativa:

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;
}

Exoneração de responsabilidade: instanceof e extensão do Foo.prototype funcionam normalmente, Foo.length funciona. .constructor e estáticos não, mas podem ser corrigidos adicionando Foo.prototype.constructor = Foo; e Object.setPrototypeOf(Foo, _Foo) se necessário.

Para subclassificar Foo (não _Foo ) com a class Bar extends Foo … , você deve usar return Reflect.construct(_Foo, args, new.target) vez da new _Foo chamada new _Foo . A subclassificação no estilo ES5 (com Foo.call(this, …) ) não é possível.


O construtor da classe de chamada manualmente pode ser útil ao refatorar o código (com partes do código no ES6, outras partes funcionando como função e definição de protótipo)

Acabei com uma pequena clichê útil, porém útil, dividindo o construtor em outra função. Período.

 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() {}

Tudo bem, eu tenho outra resposta aqui, e acho que essa é bastante inovadora.

Basicamente, o problema de fazer algo semelhante à resposta de Naomik é que você cria funções toda vez que interconecta métodos.

EDIT: Esta solução compartilha o mesmo problema, no entanto, esta resposta está sendo deixada para fins educacionais.

Então, aqui estou oferecendo uma maneira de apenas vincular novos valores aos seus métodos - que são basicamente apenas funções independentes. Isso oferece o benefício adicional de poder importar funções de diferentes módulos para o objeto recém-construído.

Ok, então aqui vai.

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)

Agora você pode fazer

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

Você também pode fazer

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

Você também tem o benefício adicional de poder exportar de módulos como tais

export const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)

Enquanto você não obtém o ClassInstance.constructor você possui o FunctorInstance.name (note que pode ser necessário preencher novamente Function.name e / ou não usar uma função de seta para exportar para compatibilidade do navegador com os objetivos Function.name )

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

PS - Sugestões de novos nomes para prepareInstance são bem-vindas, consulte Gist.

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


Você não pode usar uma classe sem o novo construtor; no meu caso, eu não queria usar o new construtor a qualquer momento que desejasse usar minha classe; portanto, o que você pode fazer é agrupar sua classe da seguinte forma (no meu caso é uma biblioteca de utilitários do Datas):





instance