単純なクラスを考える
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
クラスには「コンストラクター」という「クラス本体」があります。
内部constructor()
関数を使用する場合、その関数も同じクラス本体であり、クラスが呼び出されたときに呼び出されるため、クラスは常にコンストラクターです。
コンストラクターは、new
演算子を使用して新しいインスタンスを作成する必要があります。そのため、new
演算子なしでクラスを呼び出すと、クラスコンストラクターの必須であるため、エラーが発生します新しいインスタンスを作成します。
エラーメッセージも非常に具体的で正しいです
TypeError: 'new'なしではクラスコンストラクターを呼び出すことはできません
あなたは出来る;
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をスローする必要があると厳密に述べていますが、同時に、目的の結果を正確に達成するために使用できる機能、つまり Proxies を提供しています。
プロキシを使用すると、オブジェクトの概念を仮想化できます。たとえば、特定のオブジェクトの一部の動作を、他に影響を与えることなく変更するために使用できます。
特定のユースケースでは、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
を使用する必要はありません。勝ち勝ち。
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
いいえ、これは不可能です。 class
キーワードを使用して作成されたコンストラクターは、 [[call]] ed である場合にのみ、new
で構築できます。常になしでthrow
a 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)
if必須。
_Foo
でFoo
(class Bar extends Foo …
ではなく)をサブクラス化するには、new _Foo
呼び出しの代わりにreturn Reflect.construct(_Foo, args, new.target)
を使用する必要があります。 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();
私はあなたのためにこの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
ここで別の答えがありますが、これはかなり革新的だと思います。
基本的に、Naomikの答えに似た何かをすることの問題は、メソッドを連鎖させるたびに関数を作成することです。
編集:このソリューションは同じ問題を共有していますが、この答えは教育目的のために残されています。
そこで、ここでは、新しい値をメソッドに単にバインドする方法を提供します。これは基本的に独立した関数です。これには、異なるモジュールから新しく構築されたオブジェクトに関数をインポートできるという追加の利点があります。
さて、ここに行きます。
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
他の回答に記載されている変換関数で変換されたクラスの拡張に問題がありました。問題は、ノード(v9.4.0時点)が引数スプレッド演算子((...args) =>
)を適切にサポートしていないようです。
Classy-decorator( 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
ボーナスとして、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;
}());
「スコープセーフコンストラクター」を使用できる場所は次のとおりです。このコードを確認してください。
function Student(name) {
if(this instanceof Student) {
this.name = name;
} else {
return new Student(name);
}
}
次のように、newを使用せずにStudentオブジェクトを作成できるようになりました。
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() {}
これをnaomikのコメントへのフォローアップとして追加し、TimとBergiが示した方法を利用しています。また、一般的なケースとして使用する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()
のようなことをします