.apply() + 'new'操作符。这可能吗?

在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()。然而,人们对这个问题提出了许多非常有趣的解决方案。

我的首选解决方案是这个来自马修·克拉姆利(我已经修改了它,以传递arguments属性):

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


return function() {
return new F(arguments);
}
})();
82702 次浏览

如果您对基于求值的解决方案感兴趣

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

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

下面是一个通用的解决方案,它可以调用带有参数数组的任何构造函数(除了作为函数调用时表现不同的本机构造函数,如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污染全局命名空间。它有时被称为模块模式。

(更新)

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

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

你不能像调用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的回答。这种形式具有将临时类存储在闭包中所获得的轻微性能优势,以及可以使用一个函数创建任何类的灵活性

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]);来使用

假设你有一个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());

当运行时输出10,因为1 + 2 + 3 + 4 == 10:

$ node t.js
10

看看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(){});
同样有趣的是,可以看到重用临时F()构造函数的问题是如何通过使用arguments.callee(即创建者/工厂函数本身)来解决的: http://www.dhtmlkitchen.com/?category=/JavaScript/&date=2008/05/11/&entry=Decorator-Factory-Aspect < / p >

这难道不行吗?半睡半醒,没有仔细读书。

var Storage = undefined;


return ((Storage = (new Something(...))) == undefined? (undefined) : (Storage.apply(...)));

感谢这里的帖子,我这样使用它:

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

马修·克拉姆利的解决方案在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
)()

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

更新:我已经改变了我的第一个例子,对任何数量的参数求和,而不是只有两个。

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

This和eval-based解决方案是唯一总是有效的,即使使用Date这样的特殊构造函数:

var date = newCall(Date, 2012, 1);
console.log(date instanceof Date); // true

编辑

稍微解释一下: 我们需要在一个接受有限数量参数的函数上运行newbind方法允许我们这样做:

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

实际上,根本不需要临时f变量:

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

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

任何函数(甚至是构造函数)都可以接受数量可变的参数。每个函数都有一个“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
< p > @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;
}
function createSomething() {
var args = Array.prototype.concat.apply([null], arguments);
return new (Function.prototype.bind.apply(Something, args));
}

如果你的目标浏览器不支持ECMAScript 5 Function.prototype.bind,代码将无法工作。但这不太可能,参见选择表

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

我对它进行了测试,它似乎在所有场景下都能很好地工作。它也适用于像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)

作为一个迟来的回答,我想我只是把它放在这里作为一个更完整的解决方案,使用了这里已经概述的许多原则。

Implements.js .js

首先,这里有一个基本用法:

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

我遇到了这个问题,我是这样解决的:

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(){
// use 'new' operator to instantiate a 'Something' object
var tmp = new Something();


// If the interpreter supports [JavaScript 1.8.5][2], use 'Object.create'
// var tmp = Object.create(Something.prototype);


// calling the constructor again to initialize the object
Something.apply(tmp, arguments);
return tmp;
}

此构造函数方法可以使用new关键字,也可以不使用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上的支持表

这是一个在控制台输出中查看它的运行情况

修改了@Matthew的答案。在这里,我可以像往常一样传递任意数量的参数给函数(不是数组)。此外,'Something'也没有硬编码:

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

如果你的环境支持ECMA Script 2015的扩展操作符(...),你可以像这样简单地使用它

function Something() {
// init stuff
}


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

注意:现在ECMA Script 2015的规范已经发布,大多数JavaScript引擎都在积极地实现它,这将是最好的方式。

你可以在一些主要环境中检查扩展操作符的支持,在这里

是的,我们可以,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"的对象时,我们创建的对象继承了getAge(),但如果我们使用apply(...) or call(...)调用Actor,那么我们为"this"传递了一个对象,但我们传递的WON'T对象继承自Actor.prototype

除非,我们直接通过apply或调用Actor。原型但是....“this”指向“Actor”。而this.name将写入:Actor.prototype.name。因此影响所有用__abc1创建的其他对象,因为我们覆盖了原型而不是实例

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; . c

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);
function F(a){this.a=a}
Z=F;
f=Function('return new function '+F.name+' ()
{return  Z.apply(this,[1]) } ').call()
console.log(f)


function F(a){this.a=a}
f= new function(){return F.apply(this,[1])}
console.log(f)

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

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}

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

Reflect.construct(F, args)

虽然其他方法是可行的,但它们过于复杂。在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)都是普通的、不做任何事情的构造函数。您只需传入参数并将它们分配给同名的属性。复杂的细节放在构造函数中(例如person)。

创建这些额外的构造函数没有什么麻烦,因为它们是一种很好的实践。它们很方便,因为它们允许您潜在地拥有几个具有不同细微差别的构造函数。

这个工作!

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

因为ES6可以通过扩展操作符实现,请参见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Apply_for_new

这个答案已经在评论https://stackoverflow.com/a/42027742/7049810中给出了,但似乎被大多数人忽略了

解决方案没有 ES6或polyfills:

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);
< p > < br > 输出 < br >
演示{arg1: "X", arg2: "Y", arg3: "Z"} < br >

... 或者“更短”的方式:

var func = new Function("return function " + Demo.name + "() { };")();
func.prototype = Demo.prototype;
var obj = new func();


Demo.apply(obj, ["X", "Y", "Z"]);
< p > < br > 编辑: < br > 我认为这可能是一个很好的解决方案:

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

其实最简单的方法是:

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}

@jordancpaul的答案修改后的解决方案。

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

创建一个匿名原型,并使用参数将Something原型应用于它,然后创建该匿名原型的新实例。这样做的一个缺点是它不会通过s instanceof Something检查,尽管它是相同的,但它基本上是一个克隆的实例。

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