JavaScriptでは、(new
演算子を使用して)オブジェクトインスタンスを作成しますが、コンストラクタに任意の数の引数を渡します。これは可能ですか?
私がやりたいのはこのようなものです(ただし、以下のコードは機能しません):
function Something(){
// init stuff
}
function createSomething(){
return new Something.apply(null, arguments);
}
var s = createSomething(a,b,c); // 's' is an instance of Something
答え
ここでの応答から、new
演算子で.apply()
を呼び出す組み込みの方法がないことが明らかになりました。しかし、人々はこの問題に対して本当に興味深い解決策をいくつか提案しました。
私の優先解決策は Matthew Crumleyのこれ (arguments
プロパティを渡すように修正しました):
var createSomething = (function() {
function F(args) {
return Something.apply(this, args);
}
F.prototype = Something.prototype;
return function() {
return new F(arguments);
}
})();
ECMAScript5の Function.prototype.bind
で、物事はかなりきれいになります:
function newCall(Cls) {
return new (Function.prototype.bind.apply(Cls, arguments));
// or even
// return new (Cls.bind.apply(Cls, arguments));
// if you know that Cls.bind has not been overwritten
}
次のように使用できます。
var s = newCall(Something, a, b, c);
または直接でも:
var s = new (Function.prototype.bind.call(Something, null, a, b, c));
var s = new (Function.prototype.bind.apply(Something, [null, a, b, c]));
これと eval-based solution は、Date
のような特別なコンストラクターでも、常に機能する唯一のものです。
var date = newCall(Date, 2012, 1);
console.log(date instanceof Date); // true
編集
ちょっとした説明:限られた数の引数を取る関数でnew
を実行する必要があります。 bind
メソッドを使用すると、次のようにできます。
var f = Cls.bind(anything, arg1, arg2, ...);
result = new f();
anything
キーワードはnew
のコンテキストをリセットするため、f
パラメーターは重要ではありません。ただし、構文上の理由から必要です。ここで、bind
呼び出しの場合:可変数の引数を渡す必要があるため、これはトリックです。
var f = Cls.bind.apply(Cls, [anything, arg1, arg2, ...]);
result = new f();
それを関数でラップしましょう。 Cls
はarugment 0として渡されるため、anything
になります。
function newCall(Cls /*, arg1, arg2, ... */) {
var f = Cls.bind.apply(Cls, arguments);
return new f();
}
実際には、一時的なf
変数はまったく必要ありません。
function newCall(Cls /*, arg1, arg2, ... */) {
return new (Cls.bind.apply(Cls, arguments))();
}
最後に、bind
が本当に必要なものであることを確認する必要があります。 (Cls.bind
は上書きされた可能性があります)。したがって、それをFunction.prototype.bind
に置き換えると、上記の最終結果が得られます。
引数の配列で任意のコンストラクター(String
、Number
、Date
などの関数として呼び出されたときに異なる動作をするネイティブコンストラクターを除く)を呼び出すことができる一般的なソリューションを次に示します。
function construct(constructor, args) {
function F() {
return constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
construct(Class, [1, 2, 3])
を呼び出して作成されたオブジェクトは、new Class(1, 2, 3)
で作成されたオブジェクトと同一です。
また、より具体的なバージョンを作成して、コンストラクターを毎回渡す必要がないようにすることもできます。これは、呼び出すたびに内部関数の新しいインスタンスを作成する必要がないため、わずかに効率的です。
var createSomething = (function() {
function F(args) {
return Something.apply(this, args);
}
F.prototype = Something.prototype;
return function(args) {
return new F(args);
}
})();
そのような外部匿名関数を作成して呼び出す理由は、関数F
がグローバル名前空間を汚染しないようにするためです。モジュールパターンとも呼ばれます。
[UPDATE]
F
が何かを返すとTSがエラーを出すため、TypeScriptでこれを使用したい人のために:
function construct(constructor, args) {
function F() : void {
constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
環境が ECMA Script 2015のスプレッド演算子(...
) をサポートしている場合、次のように使用できます。
function Something() {
// init stuff
}
function createSomething() {
return new Something(...arguments);
}
注:ECMA Script 2015の仕様が公開され、ほとんどのJavaScriptエンジンが積極的に実装しているため、これは推奨される方法です。
いくつかの主要な環境 here でSpreadオペレーターのサポートを確認できます。
あなたが投げるすべての引数を丸lurみにするItemsコンストラクターがあるとします:
function Items () {
this.elems = [].slice.call(arguments);
}
Items.prototype.sum = function () {
return this.elems.reduce(function (sum, x) { return sum + x }, 0);
};
Object.create()でインスタンスを作成してから、そのインスタンスで.apply()を作成できます。
var items = Object.create(Items.prototype);
Items.apply(items, [ 1, 2, 3, 4 ]);
console.log(items.sum());
1 + 2 + 3 + 4 == 10であるため、実行時に10を印刷します。
$ node t.js
10
ES6では、 Reflect.construct()
は非常に便利です。
Reflect.construct(F, args)
@Matthewコンストラクタープロパティも修正する方が良いと思います。
// Invoke new operator with arbitrary arguments
// Holy Grail pattern
function invoke(constructor, args) {
var f;
function F() {
// constructor returns **this**
return constructor.apply(this, args);
}
F.prototype = constructor.prototype;
f = new F();
f.constructor = constructor;
return f;
}
@Matthewの回答の改良版。この形式には、一時クラスをクロージャーに格納することで得られるわずかなパフォーマンス上の利点と、任意のクラスを作成するために使用できる1つの関数を持つという柔軟性があります。
var applyCtor = function(){
var tempCtor = function() {};
return function(ctor, args){
tempCtor.prototype = ctor.prototype;
var instance = new tempCtor();
ctor.prototype.constructor.apply(instance,args);
return instance;
}
}();
これはapplyCtor(class, [arg1, arg2, argn]);
を呼び出すことで使用されます
InitをSomething
のプロトタイプの別のメソッドに移動できます。
function Something() {
// Do nothing
}
Something.prototype.init = function() {
// Do init stuff
};
function createSomething() {
var s = new Something();
s.init.apply(s, arguments);
return s;
}
var s = createSomething(a,b,c); // 's' is an instance of Something
この答えは少し遅れていますが、これを見た人は誰でもそれを使用できるかもしれないと考えました。 applyを使用して新しいオブジェクトを返す方法があります。ただし、オブジェクト宣言を少し変更する必要があります。
function testNew() {
if (!( this instanceof arguments.callee ))
return arguments.callee.apply( new arguments.callee(), arguments );
this.arg = Array.prototype.slice.call( arguments );
return this;
}
testNew.prototype.addThem = function() {
var newVal = 0,
i = 0;
for ( ; i < this.arg.length; i++ ) {
newVal += this.arg[i];
}
return newVal;
}
testNew( 4, 8 ) === { arg : [ 4, 8 ] };
testNew( 1, 2, 3, 4, 5 ).addThem() === 15;
最初のif
ステートメントがtestNew
で機能するには、関数の下部にあるreturn this;
が必要です。あなたのコードの例として:
function Something() {
// init stuff
return this;
}
function createSomething() {
return Something.apply( new Something(), arguments );
}
var s = createSomething( a, b, c );
更新:最初の例を、2つではなく、任意の数の引数を合計するように変更しました。
私はこの問題に出会ったばかりで、次のように解決しました。
function instantiate(ctor) {
switch (arguments.length) {
case 1: return new ctor();
case 2: return new ctor(arguments[1]);
case 3: return new ctor(arguments[1], arguments[2]);
case 4: return new ctor(arguments[1], arguments[2], arguments[3]);
//...
default: throw new Error('instantiate: too many parameters');
}
}
function Thing(a, b, c) {
console.log(a);
console.log(b);
console.log(c);
}
var thing = instantiate(Thing, 'abc', 123, {x:5});
ええ、それは少しいですが、問題を解決し、非常に簡単です。
評価ベースのソリューションに興味がある場合
function createSomething() {
var q = [];
for(var i = 0; i < arguments.length; i++)
q.Push("arguments[" + i + "]");
return eval("new Something(" + q.join(",") + ")");
}
CoffeeScriptの仕組みもご覧ください。
s = new Something([a,b,c]...)
になる:
var s;
s = (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Something, [a, b, c], function(){});
これは動作します!
var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);
このコンストラクターのアプローチは、new
キーワードの有無にかかわらず機能します。
function Something(foo, bar){
if (!(this instanceof Something)){
var obj = Object.create(Something.prototype);
return Something.apply(obj, arguments);
}
this.foo = foo;
this.bar = bar;
return this;
}
Object.create
のサポートを前提としていますが、古いブラウザーをサポートしている場合はいつでもポリフィルできます。 MDNのサポート表を参照 。
解決策なし ES6またはポリフィル:
var obj = _new(Demo).apply(["X", "Y", "Z"]);
function _new(constr)
{
function createNamedFunction(name)
{
return (new Function("return function " + name + "() { };"))();
}
var func = createNamedFunction(constr.name);
func.prototype = constr.prototype;
var self = new func();
return { apply: function(args) {
constr.apply(self, args);
return self;
} };
}
function Demo()
{
for(var index in arguments)
{
this['arg' + (parseInt(index) + 1)] = arguments[index];
}
}
Demo.prototype.tagged = true;
console.log(obj);
console.log(obj.tagged);
出力
デモ{arg1: "X"、arg2: "Y"、arg3: "Z"}
...または「より短い」方法:
var func = new Function("return function " + Demo.name + "() { };")();
func.prototype = Demo.prototype;
var obj = new func();
Demo.apply(obj, ["X", "Y", "Z"]);
編集:
これは良い解決策になると思います。
this.forConstructor = function(constr)
{
return { apply: function(args)
{
let name = constr.name.replace('-', '_');
let func = (new Function('args', name + '_', " return function " + name + "() { " + name + "_.apply(this, args); }"))(args, constr);
func.constructor = constr;
func.prototype = constr.prototype;
return new func(args);
}};
}
このワンライナーはそれを行う必要があります:
new (Function.prototype.bind.apply(Something, [null].concat(arguments)));
function createSomething() {
var args = Array.prototype.concat.apply([null], arguments);
return new (Function.prototype.bind.apply(Something, args));
}
ターゲットブラウザがECMAScript 5 Function.prototype.bind
をサポートしていない場合、コードは機能しません。ただし、あまり可能性はありません。 互換性テーブル を参照してください。
new
演算子を使用して、必要な数の引数を指定してコンストラクターを呼び出すことはできません。
できることは、コンストラクターをわずかに変更することです。の代わりに:
function Something() {
// deal with the "arguments" array
}
var obj = new Something.apply(null, [0, 0]); // doesn't work!
代わりにこれを行います:
function Something(args) {
// shorter, but will substitute a default if args.x is 0, false, "" etc.
this.x = args.x || SOME_DEFAULT_VALUE;
// longer, but will only put in a default if args.x is not supplied
this.x = (args.x !== undefined) ? args.x : SOME_DEFAULT_VALUE;
}
var obj = new Something({x: 0, y: 0});
または、配列を使用する必要がある場合:
function Something(args) {
var x = args[0];
var y = args[1];
}
var obj = new Something([0, 0]);
@Matthewの回答を修正しました。ここでは、(配列ではなく)通常どおり機能する任意の数のパラメーターを渡すことができます。また、「何か」は次のコードにハードコーディングされていません。
function createObject( constr ) {
var args = arguments;
var wrapper = function() {
return constr.apply( this, Array.prototype.slice.call(args, 1) );
}
wrapper.prototype = constr.prototype;
return new wrapper();
}
function Something() {
// init stuff
};
var obj1 = createObject( Something, 1, 2, 3 );
var same = new Something( 1, 2, 3 );
Matthew Crumleyのソリューション CoffeeScriptの場合:
construct = (constructor, args) ->
F = -> constructor.apply this, args
F.prototype = constructor.prototype
new F
または
createSomething = (->
F = (args) -> Something.apply this, args
F.prototype = Something.prototype
return -> new Something arguments
)()
すべての関数(コンストラクターを含む)は、可変個の引数を取ることができます。各関数には「引数」変数があり、[].slice.call(arguments)
を使用して配列にキャストできます。
function Something(){
this.options = [].slice.call(arguments);
this.toString = function (){
return this.options.toString();
};
}
var s = new Something(1, 2, 3, 4);
console.log( 's.options === "1,2,3,4":', (s.options == '1,2,3,4') );
var z = new Something(9, 10, 11);
console.log( 'z.options === "9,10,11":', (z.options == '9,10,11') );
上記のテストでは、次の出力が生成されます。
s.options === "1,2,3,4": true
z.options === "9,10,11": true
他のアプローチは実行可能ですが、過度に複雑です。 Clojureでは、通常、型/レコードをインスタンス化する関数を作成し、その関数をインスタンス化のメカニズムとして使用します。これをJavaScriptに翻訳する:
function Person(surname, name){
this.surname = surname;
this.name = name;
}
function person(surname, name){
return new Person(surname, name);
}
このアプローチを取ることにより、上記以外のnew
の使用を避けることができます。そしてもちろん、この関数はapply
やその他の関数型プログラミング機能を使用しても問題ありません。
var doe = _.partial(person, "Doe");
var john = doe("John");
var jane = doe("Jane");
このアプローチを使用することにより、すべての型コンストラクター(Person
など)はVanillaであり、何もしないコンストラクターです。引数を渡して、同じ名前のプロパティに割り当てるだけです。毛むくじゃらの詳細は、コンストラクター関数(例:person
)に入ります。
これらの追加のコンストラクター関数は、とにかく良い方法であるため、作成する必要はほとんどありません。微妙に異なる複数のコンストラクター関数を潜在的に持つことができるため、便利です。
一時的なF()
コンストラクターの再利用の問題が、arguments.callee
(別名作成者/ファクトリー関数自体)を使用することでどのように解決されたかを見るのも興味深いです。 http://www.dhtmlkitchen.com/?category=/JavaScript/&date = 2008/05/11 /&entry = Decorator-Factory-Aspect
function FooFactory() {
var prototype, F = function(){};
function Foo() {
var args = Array.prototype.slice.call(arguments),
i;
for (i = 0, this.args = {}; i < args.length; i +=1) {
this.args[i] = args[i];
}
this.bar = 'baz';
this.print();
return this;
}
prototype = Foo.prototype;
prototype.print = function () {
console.log(this.bar);
};
F.prototype = prototype;
return Foo.apply(new F(), Array.prototype.slice.call(arguments));
}
var foo = FooFactory('a', 'b', 'c', 'd', {}, function (){});
console.log('foo:',foo);
foo.print();
実際、最も簡単な方法は次のとおりです。
function Something (a, b) {
this.a = a;
this.b = b;
}
function createSomething(){
return Something;
}
s = new (createSomething())(1, 2);
// s == Something {a: 1, b: 2}
createSomething
の私のバージョンは次のとおりです。
function createSomething() {
var obj = {};
obj = Something.apply(obj, arguments) || obj;
obj.__proto__ = Something.prototype; //Object.setPrototypeOf(obj, Something.prototype);
return o;
}
それに基づいて、JavaScriptのnew
キーワードをシミュレートしようとしました。
//JavaScript 'new' keyword simulation
function new2() {
var obj = {}, args = Array.prototype.slice.call(arguments), fn = args.shift();
obj = fn.apply(obj, args) || obj;
Object.setPrototypeOf(obj, fn.prototype); //or: obj.__proto__ = fn.prototype;
return obj;
}
私はそれをテストしましたが、すべてのシナリオで完璧に機能するようです。また、Date
などのネイティブコンストラクターでも機能します。以下にいくつかのテストを示します。
//test
new2(Something);
new2(Something, 1, 2);
new2(Date); //"Tue May 13 2014 01:01:09 GMT-0700" == new Date()
new2(Array); //[] == new Array()
new2(Array, 3); //[undefined × 3] == new Array(3)
new2(Object); //Object {} == new Object()
new2(Object, 2); //Number {} == new Object(2)
new2(Object, "s"); //String {0: "s", length: 1} == new Object("s")
new2(Object, true); //Boolean {} == new Object(true)
はい、できます。javascriptは本質的にprototype inheritance
のようなものです。
function Actor(name, age){
this.name = name;
this.age = age;
}
Actor.prototype.name = "unknown";
Actor.prototype.age = "unknown";
Actor.prototype.getName = function() {
return this.name;
};
Actor.prototype.getAge = function() {
return this.age;
};
「new
」でオブジェクトを作成すると、作成されたオブジェクトはINHERITS getAge
()になりますが、apply(...) or call(...)
を使用してActorを呼び出すと、"this"
にオブジェクトを渡しますが、WON'T
に渡すオブジェクトはActor.prototype
から継承します
ただし、applyを直接渡すか、Actor.prototypeを呼び出しますが、....「this」は「Actor.prototype」を指し、this.nameはActor.prototype.name
に書き込みます。したがって、インスタンスではなくプロトタイプを上書きするため、Actor...
_で作成された他のすべてのオブジェクトに影響します
var rajini = new Actor('Rajinikanth', 31);
console.log(rajini);
console.log(rajini.getName());
console.log(rajini.getAge());
var kamal = new Actor('kamal', 18);
console.log(kamal);
console.log(kamal.getName());
console.log(kamal.getAge());
apply
で試してみましょう
var vijay = Actor.apply(null, ["pandaram", 33]);
if (vijay === undefined) {
console.log("Actor(....) didn't return anything
since we didn't call it with new");
}
var ajith = {};
Actor.apply(ajith, ['ajith', 25]);
console.log(ajith); //Object {name: "ajith", age: 25}
try {
ajith.getName();
} catch (E) {
console.log("Error since we didn't inherit ajith.prototype");
}
console.log(Actor.prototype.age); //Unknown
console.log(Actor.prototype.name); //Unknown
Actor.prototype
を最初の引数としてActor.call()
に渡すことにより、Actor()関数が実行されると、this.name=name
を実行します。「this」はActor.prototype
、this.name=name; means Actor.prototype.name=name;
を指すためです
var simbhu = Actor.apply(Actor.prototype, ['simbhu', 28]);
if (simbhu === undefined) {
console.log("Still undefined since the function didn't return anything.");
}
console.log(Actor.prototype.age); //simbhu
console.log(Actor.prototype.name); //28
var copy = Actor.prototype;
var dhanush = Actor.apply(copy, ["dhanush", 11]);
console.log(dhanush);
console.log("But now we've corrupted Parent.prototype in order to inherit");
console.log(Actor.prototype.age); //11
console.log(Actor.prototype.name); //dhanush
元の質問new operator with apply
の使用方法に戻って、ここに私の見解があります...
Function.prototype.new = function(){
var constructor = this;
function fn() {return constructor.apply(this, args)}
var args = Array.prototype.slice.call(arguments);
fn.prototype = this.prototype;
return new fn
};
var thalaivar = Actor.new.apply(Parent, ["Thalaivar", 30]);
console.log(thalaivar);
@jordancpaulの回答から修正されたソリューション。
var applyCtor = function(ctor, args)
{
var instance = new ctor();
ctor.prototype.constructor.apply(instance, args);
return instance;
};
eS6以降、これはSpread演算子を介して可能です。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Apply_for_new を参照してください。
この答えはすでに、コメントで与えられたようなものでした https://stackoverflow.com/a/42027742/704981 ですが、ほとんどの人が見逃していたようです