web-dev-qa-db-ja.com

'new'演算子での.apply()の使用。これは可能ですか?

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);
    }
})();
443
Premasagar

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に置き換えると、上記の最終結果が得られます。

349

引数の配列で任意のコンストラクター(StringNumberDateなどの関数として呼び出されたときに異なる動作をするネイティブコンストラクターを除く)を呼び出すことができる一般的なソリューションを次に示します。

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();
}
259
Matthew Crumley

環境が ECMA Script 2015のスプレッド演算子(... をサポートしている場合、次のように使用できます。

function Something() {
    // init stuff
}

function createSomething() {
    return new Something(...arguments);
}

注:ECMA Script 2015の仕様が公開され、ほとんどのJavaScriptエンジンが積極的に実装しているため、これは推奨される方法です。

いくつかの主要な環境 here でSpreadオペレーターのサポートを確認できます。

29
thefourtheye

あなたが投げるすべての引数を丸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
27
substack

ES6では、 Reflect.construct() は非常に便利です。

Reflect.construct(F, args)
14
gfaceless

@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;
}
9
wukong

@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]);を呼び出すことで使用されます

8
jordancpaul

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
7
Tim Down

この答えは少し遅れていますが、これを見た人は誰でもそれを使用できるかもしれないと考えました。 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つではなく、任意の数の引数を合計するように変更しました。

6
Trevor Norris

私はこの問題に出会ったばかりで、次のように解決しました。

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

ええ、それは少しいですが、問題を解決し、非常に簡単です。

6
alekop

評価ベースのソリューションに興味がある場合

function createSomething() {
    var q = [];
    for(var i = 0; i < arguments.length; i++)
        q.Push("arguments[" + i + "]");
    return eval("new Something(" + q.join(",") + ")");
}
4
user187291

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

これは動作します!

var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);
3
infinito84

このコンストラクターのアプローチは、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のサポート表を参照

JSBinはコンソール出力で動作を確認するため です。

2
muffinresearch

解決策なし 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);
    }};
}
2
Martin Wantke

このワンライナーはそれを行う必要があります:

new (Function.prototype.bind.apply(Something, [null].concat(arguments)));
1
aleemb
function createSomething() {
    var args = Array.prototype.concat.apply([null], arguments);
    return new (Function.prototype.bind.apply(Something, args));
}

ターゲットブラウザがECMAScript 5 Function.prototype.bindをサポートしていない場合、コードは機能しません。ただし、あまり可能性はありません。 互換性テーブル を参照してください。

1
user2683246

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]);
1
Anthony Mills

@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 );
1
Eugen Konkov

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
)()
1
Benjie

すべての関数(コンストラクターを含む)は、可変個の引数を取ることができます。各関数には「引数」変数があり、[].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
0
Wil Moore III

他のアプローチは実行可能ですが、過度に複雑です。 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)に入ります。

これらの追加のコンストラクター関数は、とにかく良い方法であるため、作成する必要はほとんどありません。微妙に異なる複数のコンストラクター関数を潜在的に持つことができるため、便利です。

0
Mario

一時的なF()コンストラクターの再利用の問題が、arguments.callee(別名作成者/ファクトリー関数自体)を使用することでどのように解決されたかを見るのも興味深いです。 http://www.dhtmlkitchen.com/?category=/JavaScript/&date = 2008/05/11 /&entry = Decorator-Factory-Aspect

0
polaretto
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();
0
user2217522

実際、最も簡単な方法は次のとおりです。

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}
0
user3184743

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)
0
advncd

はい、できます。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.prototypethis.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);
0
Thalaivar

@jordancpaulの回答から修正されたソリューション。

var applyCtor = function(ctor, args)
{
    var instance = new ctor();
    ctor.prototype.constructor.apply(instance, args);
    return instance;
}; 
0
tech-e

eS6以降、これはSpread演算子を介して可能です。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Apply_for_new を参照してください。

この答えはすでに、コメントで与えられたようなものでした https://stackoverflow.com/a/42027742/704981 ですが、ほとんどの人が見逃していたようです

0
Paul