javascript - ejemplos - ES6: constructor de clase de llamada sin nueva palabra clave
javascript class properties (15)
Acabo de hacer este módulo npm para ti;)
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
Dada una clase simple
class Foo {
constructor(x) {
if (!(this instanceof Foo)) return new Foo(x);
this.x = x;
}
hello() {
return `hello ${this.x}`;
}
}
¿Es posible llamar al constructor de la clase sin la
new
palabra clave?
El uso debe permitir
(new Foo("world")).hello(); // "hello world"
O
Foo("world").hello(); // "hello world"
Pero este último falla con
Cannot call a class as a function
Aquí hay un lugar donde puede usar un ''constructor seguro de alcance''. Observe este código:
function Student(name) {
if(this instanceof Student) {
this.name = name;
} else {
return new Student(name);
}
}
Ahora puede crear un objeto Estudiante sin usar nuevo de la siguiente manera:
var stud1 = Student(''Kia'');
Aquí hay un patrón que he encontrado que realmente me ayuda.
No usa una
class
, pero tampoco requiere el uso de
new
.
Ganar / ganar.
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
Bien, tengo otra respuesta aquí, y creo que esta es bastante innovadora.
Básicamente, el problema de hacer algo similar a la respuesta de Naomik es que usted crea funciones cada vez que encadena métodos juntos.
EDITAR: Esta solución comparte el mismo problema, sin embargo, esta respuesta se deja para fines educativos.
Así que aquí estoy ofreciendo una manera de vincular simplemente nuevos valores a sus métodos, que son básicamente funciones independientes. Esto ofrece el beneficio adicional de poder importar funciones de diferentes módulos en el objeto recién construido.
De acuerdo, así que aquí va.
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)
Ahora puedes hacer
Right(4)
.map(x=>x+1)
.map(x=>x*2)
.inspect()
Tambien puedes hacer
Right.of(4)
.map(x=>x+1)
.map(x=>x*2)
.inspect()
También tiene el beneficio adicional de poder exportar desde módulos como tal
export const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)
Si bien no obtiene
ClassInstance.constructor
, sí tiene
FunctorInstance.name
(tenga en cuenta que es posible que deba rellenar
Function.name
y / o no usar una función de flecha para exportar para la compatibilidad del navegador con fines de
Function.name
)
export function Right(...args){
return prepareInstance(RightInstanceMethods,RightStaticMethods)(...args)
}
PD: nuevas sugerencias de nombres para preparar la entrada bienvenida, ver Gist.
https://gist.github.com/babakness/56da19ba85e0eaa43ae5577bc0064456
Como otros han señalado, la especificación ES2015 establece estrictamente que dicha llamada debería arrojar TypeError, pero al mismo tiempo proporciona una función que se puede utilizar para lograr exactamente el resultado deseado, es decir, Proxies .
Proxies nos permite virtualizar sobre un concepto de un objeto. Por ejemplo, se pueden usar para cambiar algún comportamiento de un objeto en particular sin afectar nada más.
En su caso de uso específico, la
class Foo
es un
Function object
que se puede invocar; esto normalmente significa que se ejecutará el cuerpo de esta función.
Pero esto se puede cambiar con
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
(Tenga en cuenta que
_Foo
ahora es la clase que desea exponer, por lo que los identificadores probablemente deberían ser al revés)
Si lo ejecuta un navegador que admite Proxies, al llamar a
_Foo(...)
ahora se ejecutará la función de captura de
apply
lugar del constructor original.
Al mismo tiempo, esta "nueva" clase
_Foo
es indistinguible de la
Foo
original (aparte de poder llamarla como una función normal).
Del mismo modo, no hay diferencia por la cual pueda distinguir objetos creados con
Foo
y
_Foo
.
El mayor inconveniente de esto es que no puede ser transpilado o polifundido , pero sigue siendo su solución viable para que la clase tipo Scala se aplique en JS en el futuro.
Desenterró este en el borrador
Constructores definidos usando la sintaxis de definición de clase cuando se llaman como funciones
Así que supongo que eso no es posible con las clases.
El constructor de la clase de llamada manualmente puede ser útil cuando se refactoriza el código (que tiene partes del código en ES6, otras partes de la función beeing y definición del prototipo)
Terminé con un pequeño, pero útil repetitivo, dividiendo el constructor en otra función. 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() {}
Esto puede ser un poco artificial, pero funciona
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()
Estoy agregando esto como seguimiento a un comentario de naomik y utilizo el método ilustrado por Tim y Bergi. También voy a sugerir una función para usar como un caso general.
Para hacer esto de una manera funcional Y utilizar la eficiencia de los prototipos (no volver a crear todo el método cada vez que se crea una nueva instancia), se podría usar este patrón
const Foo = function(x){ this._value = x ... }
Foo.of = function(x){ return new Foo(x) }
Foo.prototype = {
increment(){ return Foo.of(this._value + 1) },
...
}
Tenga en cuenta que esto es consistente con
fantasy-land
especificaciones JS de
fantasy-land
Land
https://github.com/fantasyland/fantasy-land#of-method
Personalmente, creo que es más limpio usar la sintaxis de clase ES6
class Foo {
static of(x) { new Foo(x)}
constructor(x) { this._value = x }
increment() { Foo.of(this._value+1) }
}
Ahora uno podría envolver esto en un cierre como tal
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)
}
O cambie el nombre de
FooOf
y
Foo
como desee, es decir, la clase podría ser
FooClass
y la función solo
Foo
, etc.
Esto es mejor que colocar la clase en la función porque crear nuevas instancias no nos agobia con la creación de nuevas clases también.
Otra forma más es crear una función
const of = (classObj,...args) => (
classObj.of
? classObj.of(value)
: new classObj(args)
)
Y luego hacer algo como
of(Foo,5).increment()
Las clases tienen un "cuerpo de clase" que
es un constructor
.
Si usa una función interna
constructor()
, esa función también sería el mismo cuerpo de clase, y sería lo que se llama cuando se llama a la clase, por lo tanto, una clase siempre es un constructor.
Los constructores requieren el uso del
new
operador para crear una nueva instancia, por lo que invocar una clase sin el
new
operador genera un error, ya que el constructor de la clase debe crear una nueva instancia.
El mensaje de error también es bastante específico y correcto.
TypeError: los constructores de clase no se pueden invocar sin ''nuevo''
Tú podrías;
- use una función regular en lugar de una clase 1 .
-
Siempre llame a la clase con
new
. -
Llame a la clase dentro de una función regular de ajuste, siempre usando
new
, de esa manera obtendrá los beneficios de las clases, pero la función de ajuste aún se puede llamar con y sin elnew
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) };
No es posible llamar al constructor de la clase sin la
new
palabra clave.
El mensaje de error es bastante específico.
Vea una publicación de blog sobre 2ality y las 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
No, esto no es posible.
Los constructores que se crean con la palabra clave de
class
solo se pueden construir con
new
, si se editan
[[call]]ed
sin que siempre
throw
un
TypeError
1
(y ni siquiera hay una forma de detectar esto desde el exterior).
1: No estoy seguro de si los transpiladores hacen esto bien
Sin embargo, puede usar una función normal como solución 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;
}
Descargo de responsabilidad: la
instanceof
y la extensión de
Foo.prototype
funcionan de manera normal,
Foo.length
no,
.constructor
y los métodos estáticos no lo hacen, pero pueden solucionarse agregando
Foo.prototype.constructor = Foo;
y
Object.setPrototypeOf(Foo, _Foo)
si es necesario.
Para subclasificar
Foo
(no
_Foo
) con la
class Bar extends Foo …
, debe usar
return Reflect.construct(_Foo, args, new.target)
lugar de la
new _Foo
llamada
new _Foo
.
Subclasificar en estilo ES5 (con
Foo.call(this, …)
) no es posible.
Tuve problemas para extender las clases convertidas con la función de transformación mencionada en algunas otras respuestas.
El problema parece ser que el nodo (a partir de v9.4.0) no admite correctamente el operador de propagación de argumentos (
(...args) =>
).
Esta función basada en la salida transpilada del classy-decorator (mencionado en otra respuesta ) funciona para mí y no requiere soporte para decoradores o el operador de propagación de argumentos.
// 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;
}
Uso:
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
Como beneficio adicional, TypeScript (con salida "es5") parece estar bien con el viejo truco de
instanceof
(bueno, no escribirá verificación si se usa sin
new
pero funciona de todos modos):
class X {
constructor() {
if (!(this instanceof X)) {
return new X();
}
}
}
porque lo compila en:
var X = /** @class */ (function () {
function X() {
if (!(this instanceof X)) {
return new X();
}
}
return X;
}());
class MyClass {
constructor(param) {
// ...
}
static create(param) {
return new MyClass(param);
}
doSomething() {
// ...
}
}
MyClass.create(''Hello World'').doSomething();
¿Es eso lo que quieres?
Si necesita algo de lógica al crear una nueva instancia de
MyClass
, podría ser útil implementar una "CreationStrategy" para superar la lógica:
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();