使用.apply()和'new'操作符。 这可能吗?

在JavaScript中,我想创build一个对象实例(通过new运算符),但将任意数量的parameter passing给构造函数。 这可能吗?

我想要做的是这样的(但下面的代码不起作用):

 function Something(){ // init stuff } function createSomething(){ return new Something.apply(null, arguments); } var s = createSomething(a,b,c); // 's' is an instance of Something 

答案

从这里的回答可以明显看出,没有内build的方法可以用new运算符调用.apply() 。 但是,人们提出了一些非常有趣的解决scheme。

我最喜欢的解决scheme是从马修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的解决scheme是唯一的工作,即使有特殊的构造函数如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作为地址0传递,所以它将成为我们的anything

 function newCall(Cls /*, arg1, arg2, ... */) { var f = Cls.bind.apply(Cls, arguments); return new f(); } 

实际上,暂时的fvariables根本就不需要:

 function newCall(Cls /*, arg1, arg2, ... */) { return new (Cls.bind.apply(Cls, arguments))(); } 

最后,我们应该确保bind真的是我们需要的。 ( Cls.bind可能已被覆盖)。 所以把它replace成Function.prototype.bind ,我们得到如上的最终结果。

这是一个通用的解决scheme,可以调用任何构造函数(除了本地构造函数,当被称为函数,如StringNumberDate等)的行为有一个参数数组:

 function construct(constructor, args) { function F() { return constructor.apply(this, args); } F.prototype = constructor.prototype; return new F(); } 

通过调用construct(Class, [1, 2, 3])创build的对象将与使用new Class(1, 2, 3)创build的对象相同。

你也可以做一个更具体的版本,所以你不必每次都传递构造函数。 这也稍微有效一些,因为每次调用它时都不需要创build内部函数的新实例。

 var createSomething = (function() { function F(args) { return Something.apply(this, args); } F.prototype = Something.prototype; return function(args) { return new F(args); } })(); 

创build和调用外部匿名函数的原因是保持函数F不会污染全局名称空间。 有时称之为模块模式。

[UPDATE]

对于那些想在TypeScript中使用它的人来说,如果F返回任何东西,TS会给出一个错误:

 function construct(constructor, args) { function F() : void { constructor.apply(this, args); } F.prototype = constructor.prototype; return new F(); } 

假设你已经有了一个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()创build实例,然后使用该实例创build.apply():

 var items = Object.create(Items.prototype); Items.apply(items, [ 1, 2, 3, 4 ]); console.log(items.sum()); 

其中,当运行打印10,因为1 + 2 + 3 + 4 == 10:

 $ node t.js 10 

如果您的环境支持ECMA Script 2015的传播运算符( ... ) ,则可以简单地使用它

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

注意:现在已经发布了ECMA Script 2015的规范,大多数JavaScript引擎正在积极实施它,这将是这样做的首选方式。

在这里,您可以在几个主要环境中检查Spread运算符的支持。

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

在ES6中, Reflect.construct()非常方便:

 Reflect.construct(F, args) 

你可以将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 

@马修的答案的改进版本。 这种forms具有轻微的性能优势,通过将临时类存储在闭包中,以及具有可用于创build任何类的一个函数的灵活性

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

这个答案有点晚了,但是认为任何看到这个的人都可以使用它。 有一种方法可以使用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 ); 

更新:我已经改变了我的第一个例子来总结任何数量的参数,而不是两个。

我刚刚遇到了这个问题,我解决了这个问题:

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

是的,这有点难看,但它解决了这个问题,而且简单。

如果您对基于eval的解决scheme感兴趣

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

这个构造函数的方法可以使用或不使用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,通过控制台输出看到它的动作 。

这工作!

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

你不能使用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]); 

CoffeeScript中的Matthew Crumley解决scheme :

 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 )() 
 function createSomething() { var args = Array.prototype.concat.apply([null], arguments); return new (Function.prototype.bind.apply(Something, args)); } 

如果您的目标浏览器不支持ECMAScript 5 Function.prototype.bind ,则代码将不起作用。 这不是很有可能,看兼容性表 。

修改@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 ); 

这一行应该做到这一点:

 new (Function.prototype.bind.apply(Something, [null].concat(arguments))); 

没有 ES6或polyfills的解决scheme:

 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"]); 

编辑:
我认为这可能是一个很好的解决scheme:

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

这也是有趣的,看看如何重用临时F()构造函数的问题,通过使用arguments.callee ,也就是创build者/工厂函数本身来解决: http ://www.dhtmlkitchen.com/?category=/JavaScript/& date = 2008/05/11 /&条目=装饰-出厂看点

任何函数(甚至是构造函数)都可以带有可变数量的参数。 每个函数都有一个“参数”variables,它可以用[].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') ); 

上述testing产生以下输出:

 s.options === "1,2,3,4": true z.options === "9,10,11": true 
 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(); 

这是我的版本的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; } 

我testing了它,似乎它适用于所有情况都很好。 它也适用于像Date这样的本地构造函数。 这里有一些testing:

 //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) 

虽然其他方法是可行的,但它们过于复杂。 在Clojure中,通常会创build一个实例化types/logging的函数,并将该函数用作实例化的机制。 将此翻译成JavaScript:

 function Person(surname, name){ this.surname = surname; this.name = name; } function person(surname, name){ return new Person(surname, name); } 

通过采取这种方法,你可以避免使用new除了上面描述的。 当然,这个function在apply或任何其他function编程function方面都没有问题。

 var doe = _.partial(person, "Doe"); var john = doe("John"); var jane = doe("Jane"); 

通过使用这种方法,所有types的构造函数(例如Person )都是香草,无所作为的构造函数。 您只需传入参数并将其分配给同名的属性。 多毛的细节进入构造函数(如person )。

无论如何,创build这些额外的构造函数都是一件很好的事情。 它们可以很方便,因为它们允许你有不同的细微差别的几个构造函数。

是的,我们可以,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 ”创build一个对象时,我们创build的对象INHERITS getAge (),但是如果我们使用apply(...) or call(...)来调用Actor,那么我们传递一个"this"我们传递的对象WON'TActor.prototypeinheritance

除非我们直接通过应用或调用Actor.prototype,但是…“this”将指向“Actor.prototype”,this.name将写入: Actor.prototype.name 。 从而影响所有使用Actor...创build的其他对象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; 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); 

感谢在这里的post,我用这种方式:

 SomeClass = function(arg1, arg2) { // ... } ReflectUtil.newInstance('SomeClass', 5, 7); 

和执行:

 /** * @param strClass: * class name * @param optionals: * constructor arguments */ ReflectUtil.newInstance = function(strClass) { var args = Array.prototype.slice.call(arguments, 1); var clsClass = eval(strClass); function F() { return clsClass.apply(this, args); } F.prototype = clsClass.prototype; return new F(); }; 

作为一个迟到的答案,我尽pipe将这里放在这里作为一个更完整的解决scheme,使用这里已经列出的许多校长。

Implements.js

为了让你开始,这里是一个基本的用法:

 var a = function(){ this.propa = 'a'; } var b = function(){ this.propb = 'b' } var c = Function.Implement(a, b); // -> { propa: 'a', propb: 'b' } 

你为什么把事情弄得这么复杂 在新的使用匿名函数后返回带有参数的应用数组的构造函数。

 function myConstructor(a,b,c){ this.a = a; this.b = b; this.c = c; } var newObject = new myConstructor(1,2,3); // {a: 1, b: 2, c: 3} var myArguments = [1,2,3]; var anotherObject = new function(){ return myConstructor.apply(this,myArguments); }; // {a: 1, b: 2, c: 3}