Compte tenu d'un cours simple
class Foo {
constructor(x) {
if (!(this instanceof Foo)) return new Foo(x);
this.x = x;
}
hello() {
return `hello ${this.x}`;
}
}
Est-il possible d'appeler le constructeur de classe sans le mot clé new
?
L'utilisation devrait permettre
(new Foo("world")).hello(); // "hello world"
Ou
Foo("world").hello(); // "hello world"
Mais ce dernier échoue avec
Cannot call a class as a function
Les classes ont un "corps de classe" qui est un constructeur.
Si vous utilisez une fonction interne constructor()
, celle-ci aurait également le même corps de classe et serait appelée lorsque la classe est appelée. Une classe est donc toujours un constructeur.
Les constructeurs requièrent l’utilisation de l’opérateur new
pour créer une nouvelle instance. Par conséquent, l’appel d’une classe sans l’opérateur new
entraîne une erreur, car requis pour le constructeur de classe pour créer une nouvelle instance.
Le message d'erreur est également assez spécifique et correct
TypeError: les constructeurs de classe ne peuvent pas être appelés sans 'new'
Vous pourriez;
new
.new
pour bénéficier des avantages des classes, mais la fonction d'habillage peut toujours être appelée avec et sans l'opérateur 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) };
Comme d'autres l'ont souligné, la spécification ES2015 stipule strictement que cet appel doit renvoyer TypeError, mais fournit en même temps une fonctionnalité qui peut être utilisée pour obtenir exactement le résultat souhaité, à savoir Proxies .
Les procurations nous permettent de virtualiser sur un concept d'objet. Par exemple, ils peuvent être utilisés pour modifier certains comportements d'objets particuliers sans rien affecter d'autre.
Dans votre cas d'utilisation spécifique class Foo
Est Function object
Qui peut être appelé - cela signifie normalement que le corps de cette fonction sera exécuté. Mais cela peut être changé avec 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
(Notez que _Foo
Est maintenant la classe que vous voulez exposer, donc les identifiants devraient probablement être l'inverse.)
S'il est exécuté par un navigateur prenant en charge les mandataires, l'appel de _Foo(...)
exécutera désormais la fonction apply
trap à la place du constructeur d'origine.
En même temps, cette "nouvelle" classe _Foo
Est indiscernable de l'originale Foo
(hormis le fait de pouvoir l'appeler comme une fonction normale). De même, il n'y a pas de différence permettant de distinguer un objet créé avec Foo
et _Foo
.
Le plus gros inconvénient de ceci est que il ne peut pas être transpillé ou pollyfilled , mais reste sa solution viable pour que la classe de type Scala soit appliquée à JS à l'avenir.
Voici un motif que j'ai rencontré qui m'aide vraiment. Il n'utilise pas class
, mais il ne nécessite pas non plus l'utilisation de 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
Non, ce n'est pas possible. Les constructeurs créés avec le mot clé class
ne peuvent être construits qu'avec new
, s'ils sont [[appel]] ed sans toujours throw
un TypeError
1 (et il n'y a même pas moyen de détecter cela de l'extérieur).
1: Je ne suis pas sûr que les transpilers obtiennent ce droit
Vous pouvez utiliser une fonction normale comme solution de contournement, cependant:
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;
}
Avertissement: instanceof
et étendre Foo.prototype
Fonctionnent normalement, Foo.length
Ne le fait pas, .constructor
Et les méthodes statiques ne le font pas mais peuvent être corrigées en ajoutant Foo.prototype.constructor = Foo;
et Object.setPrototypeOf(Foo, _Foo)
si nécessaire.
Pour sous-classer Foo
(et non pas _Foo
) Avec class Bar extends Foo …
, Vous devez utiliser return Reflect.construct(_Foo, args, new.target)
au lieu de l'appel new _Foo
. Le sous-classement dans le style ES5 (avec Foo.call(this, …)
) n'est pas possible.
class MyClass {
constructor(param) {
// ...
}
static create(param) {
return new MyClass(param);
}
doSomething() {
// ...
}
}
MyClass.create('Hello World').doSomething();
Est-ce que c'est ce que tu veux?
Si vous avez besoin de logique lors de la création d'une nouvelle instance de MyClass
, il pourrait être utile d'implémenter un "CreationStrategy" pour dépasser la logique:
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();
je viens de faire ce module NPM pour vous;)
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
Creusez celui-ci dans le brouillon
Les constructeurs définis à l'aide de la syntaxe de définition de classe jettent lorsqu'ils sont appelés en tant que fonctions
Donc, je suppose que ce n'est pas possible avec les cours.
D'accord, j'ai une autre réponse ici, et je pense que celle-ci est assez novatrice.
Fondamentalement, le problème avec quelque chose de similaire à la réponse de Naomik est que vous créez des fonctions chaque fois que vous chaînez des méthodes.
EDIT: Cette solution partage le même problème, cependant, cette réponse est laissée à des fins éducatives.
Je vous propose donc ici un moyen de lier simplement de nouvelles valeurs à vos méthodes, qui sont essentiellement des fonctions indépendantes. Cela offre l’avantage supplémentaire de pouvoir importer des fonctions de différents modules dans le nouvel objet construit.
Ok, alors c'est parti.
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)
Maintenant tu peux faire
Right(4)
.map(x=>x+1)
.map(x=>x*2)
.inspect()
Vous pouvez aussi faire
Right.of(4)
.map(x=>x+1)
.map(x=>x*2)
.inspect()
Vous avez également l'avantage de pouvoir exporter des modules en tant que tels.
export const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)
Tant que vous n'obtenez pas ClassInstance.constructor
vous avez FunctorInstance.name
(notez qu'il peut être nécessaire de remplir polyfill Function.name
et/ou ne pas utiliser de fonction de flèche pour l'exportation afin d'assurer la compatibilité du navigateur avec Function.name
_ fins)
export function Right(...args){
return prepareInstance(RightInstanceMethods,RightStaticMethods)(...args)
}
PS - Nouvelles suggestions de noms pour prepareInstance, voir Gist.
https://Gist.github.com/babakness/56da19ba85e0eaa43ae5577bc0064456
J'ai eu des problèmes lors de l'extension de classes converties avec la fonction de transformation mentionnée dans d'autres réponses. Le problème semble être que le noeud (à partir de la v9.4.0) ne prend pas correctement en charge l’opérateur de propagation d’argument ((...args) =>
).
Cette fonction basée sur la sortie transcrite du classy-decorator (mentionné dans autre réponse ) fonctionne pour moi et ne nécessite pas de support pour les décorateurs ni pour l'opérateur de propagation d'argument.
// 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;
}
Usage:
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
En prime, TypeScript (avec la sortie "es5") semble bien fonctionner avec l'ancienne astuce instanceof
(enfin, il ne sera pas typecheck s'il est utilisé sans new
mais il fonctionne quand même):
class X {
constructor() {
if (!(this instanceof X)) {
return new X();
}
}
}
parce qu'il compile jusqu'à:
var X = /** @class */ (function () {
function X() {
if (!(this instanceof X)) {
return new X();
}
}
return X;
}());
Voici un endroit où vous pouvez utiliser un 'constructeur sécurisé de portée' Observez ce code:
function Student(name) {
if(this instanceof Student) {
this.name = name;
} else {
return new Student(name);
}
}
Vous pouvez maintenant créer un objet Student sans utiliser de nouveau, comme suit:
var stud1 = Student('Kia');
Le constructeur de classe d'appel peut être utile manuellement lors du refactoring de code (avoir des parties du code dans ES6, d'autres parties en tant que fonction et définition du prototype)
Je me suis retrouvé avec un passe-partout petit mais utile, découpant le constructeur en une autre fonction. Période.
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() {}
J'ajoute ceci pour faire suite à un commentaire de naomik et en utilisant la méthode illustrée par Tim et Bergi. Je vais également suggérer une fonction of
à utiliser comme cas général.
Pour faire cela de manière fonctionnelle ET utiliser l'efficacité des prototypes (ne pas recréer toutes les méthodes à chaque fois qu'une nouvelle instance est créée), on pourrait utiliser ce modèle.
const Foo = function(x){ this._value = x ... }
Foo.of = function(x){ return new Foo(x) }
Foo.prototype = {
increment(){ return Foo.of(this._value + 1) },
...
}
Veuillez noter que ceci est conforme à fantasy-land
JS specs
https://github.com/fantasyland/fantasy-land#of-method
Personnellement, j'estime qu'il est plus propre d'utiliser la syntaxe de classe ES6
class Foo {
static of(x) { new Foo(x)}
constructor(x) { this._value = x }
increment() { Foo.of(this._value+1) }
}
Maintenant, on pourrait envelopper dans une fermeture en tant que telle
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 renommez FooOf
et Foo
comme vous le souhaitez, c’est-à-dire que la classe pourrait être FooClass
et la fonction simplement Foo
, etc.
Cela vaut mieux que de placer la classe dans la fonction car la création de nouvelles instances ne nous alourdit pas avec la création de nouvelles classes.
Une autre méthode consiste à créer une fonction of
const of = (classObj,...args) => (
classObj.of
? classObj.of(value)
: new classObj(args)
)
Et ensuite, faites quelque chose comme of(Foo,5).increment()
L'appel du constructeur de la classe sans le mot clé new
n'est pas possible.
Le message d'erreur est assez spécifique.
Voir un article de blog sur 2ality et le 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