在 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()
的内置方法。然而,人们提出了一些非常有趣的解决方案来解决这个问题。
我首选的解决方案是 this one from Matthew Crumley(我已对其进行了修改以传递 arguments
属性):
var createSomething = (function() {
function F(args) {
return Something.apply(this, args);
}
F.prototype = Something.prototype;
return function() {
return new F(arguments);
}
})();
new
做了两件事:(i) 设置原型,以及 (ii) 将设置了 this
的构造函数应用于所述对象/原型组合。您可以使用 Object.create()
来实现这一点,也可以通过滚动您自己的 Object.create()
并使用闭包捕获上下文来实现。
spread
运算符。 =) 在 Chrome 中它也快一点:jsperf.com/dynamic-arguments-to-the-constructor
var s = new Something(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]));
这个和 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
作为参数 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
污染全局命名空间。它有时被称为模块模式。
[更新]
对于那些想在 TypeScript 中使用它的人,因为如果 F
返回任何内容,TS 会给出错误:
function construct(constructor, args) {
function F() : void {
constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
arguments
var。
Date
、String
或作为构造函数调用时行为不同的任何其他函数。
myObj.constructor.name
属性将为所有内容设置为 F
。在查看调试器内部的堆栈跟踪和向下钻取转储时,这有点糟糕,因为它们在任何地方都使用该名称。
如果您的环境支持 ECMA Script 2015's spread operator (...
),您可以像这样简单地使用它
function Something() {
// init stuff
}
function createSomething() {
return new Something(...arguments);
}
注意:现在 ECMA Script 2015 的规范已经发布并且大多数 JavaScript 引擎都在积极实施它,这将是执行此操作的首选方式。
您可以在几个主要环境中检查 Spread 运算符的支持,here。
thisArg
。
假设你有一个 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
Object.create
,这是另一种好方法。
Reflect.construct
可能会更好
在 ES6 中,Reflect.construct()
非常方便:
Reflect.construct(F, args)
this
应用于我正在构造“F”的新类,但似乎 F 中 this
的上下文仍然是新的,并且this
不要继续,有没有人发现任何好的博客文章或如何使用Reflect.construct()
的例子?
@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;
}
newObject.constructor == ConstructorFunction
)。事实证明根本不需要分配,它在@MatthewCrumley 的代码中设置正确。
您可以将 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
init()
方法,然后在其上使用 apply()
。正如我对 Ionut 方法的评论一样,如果不修改构造函数的体系结构就没有办法做到这一点,这有点遗憾。但这看起来是一个务实的解决方案。
@Matthew's answer的改进版本。这种形式通过将临时类存储在闭包中获得了轻微的性能优势,以及让一个函数能够用于创建任何类的灵活性
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 的解决方案感兴趣
function createSomething() {
var q = [];
for(var i = 0; i < arguments.length; i++)
q.push("arguments[" + i + "]");
return eval("new Something(" + q.join(",") + ")");
}
这行得通!
var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);
另请参阅 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
,但如果您支持旧版浏览器,您总是可以填充它。 See the support table on MDN here。
这是一个JSBin to see it in action with console output。
没有 ES6 或 polyfill 的解决方案:
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);
输出 Demo {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 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 Crumley's solutions 在 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
)()
function createSomething() {
var args = Array.prototype.concat.apply([null], arguments);
return new (Function.prototype.bind.apply(Something, args));
}
如果您的目标浏览器不支持 ECMAScript 5 Function.prototype.bind
,则代码将不起作用。但可能性不大,请参见compatibilty table。
修改了@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)));
虽然其他方法是可行的,但它们过于复杂。在 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
)。
不必费心创建这些额外的构造函数,因为无论如何它们都是一个好习惯。它们很方便,因为它们允许您潜在地拥有多个具有不同细微差别的构造函数。
arguments.length
在 Person
函数中始终为 2,而在 person
中使用 new (Function.prototype.bind.apply(Person, arguments))
也会将参数设置为 Person
中的正确值。
看看如何通过使用 arguments.callee
(即创建者/工厂函数本身)解决重用临时 F()
构造函数的问题也很有趣:http://www.dhtmlkitchen.com/?category=/JavaScript/&date=2008/05/11/&entry=Decorator-Factory-Aspect
任何函数(甚至是构造函数)都可以采用可变数量的参数。每个函数都有一个“参数”变量,可以使用 [].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
s
和 z
时,传递给 Something
的参数数量是静态的。
这是我的 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
”创建对象时,我们创建的对象会继承 getAge
(),但是如果我们使用 apply(...) or call(...)
调用 Actor,那么我们正在为 "this"
传递一个对象,但我们传递的对象是 {5 } 继承自 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);
由于 ES6,这可以通过 Spread 运算符实现,请参阅 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Apply_for_new
这个答案已经在评论 https://stackoverflow.com/a/42027742/7049810 中给出,但似乎大多数人都错过了
其实最简单的方法是:
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
感谢这里的帖子,我以这种方式使用它:
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();
};
Function.bind
代替Function.prototype.bind
,我只是为了清楚起见而留下了它。毕竟,可以使用任何函数:eval.bind
会节省更多代码,但这实在是太令人困惑了。new (Function.prototype.bind.apply(Array, [1,2,3]));
,但忘记了您的newCall
函数已经收到cls
参数。newCall(Something, a, b, c);
,其中a
将是绑定的上下文,但在您的第二个示例中,您确实提到上下文没有意义 - 所以您发送null
。对我来说,这非常令人困惑(您相信我考虑了 3 天吗?) - 无论如何,您的第一个代码(要对您的第二个示例保持不变)需要:function newCall(Cls) {var arr=Array.prototype.slice.call(arguments);arr.shift(); return new (Function.prototype.bind.apply(Cls, [null,arr]));
[null,arr]
-[null].concat(arr)