在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
答案
根据这里的回答,很明显没有内置的方法用.apply()
操作符调用new
。然而,人们提出了一些非常有趣的解决方案。
我首选的解决方案是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);
}
})();
使用ECMAScript 5,qazxsw poi的东西变得非常干净:
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();
作为arugment 0传递,所以它将是我们的Cls
。
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 Something() {
// init stuff
return this;
}
function createSomething() {
return Something.apply( new Something(), arguments );
}
var s = createSomething( a, b, c );
是的,它有点难看,但它解决了问题,而且很简单。
如果您对基于eval的解决方案感兴趣
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});
另请参阅CoffeeScript如何做到这一点。
function createSomething() {
var q = [];
for(var i = 0; i < arguments.length; i++)
q.push("arguments[" + i + "]");
return eval("new Something(" + q.join(",") + ")");
}
变为:
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
。
没有ES6或polyfill的解决方案:
JSBin to see it in action with console output
产量 演示{arg1:“X”,arg2:“Y”,arg3:“Z”} ......或“更短”的方式:
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);
编辑: 我认为这可能是一个很好的解决方案:
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});
CoffeeScript中的function Something(args) {
var x = args[0];
var y = args[1];
}
var obj = new Something([0, 0]);
:
Matthew Crumley's solutions
要么
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
)()
如果您的目标浏览器不支持ECMAScript 5 function createSomething() {
var args = Array.prototype.concat.apply([null], arguments);
return new (Function.prototype.bind.apply(Something, args));
}
,则代码将不起作用。但这不太可能,请参阅Function.prototype.bind
。
修改@Matthew回答。在这里,我可以传递任意数量的参数来照常运行(不是数组)。 'Something'也没有硬编码:
compatibilty table
这是一个通用的解决方案,可以使用参数数组调用任何构造函数(除了在被称为函数时表现不同的本机构造函数,如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);
}
})();
不会污染全局命名空间。它有时被称为模块模式。
[UPDATE]
对于那些想在TypeScript中使用它的人,因为如果F
返回任何东西,TS会给出错误:
F
这个单行应该这样做:
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)));
(又名创建者/工厂函数本身)来解决重用临时F()
构造函数的问题也很有趣:arguments.callee
任何函数(甚至构造函数)都可以使用可变数量的参数。每个函数都有一个“arguments”变量,可以使用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
这是我的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
基于此,我尝试模拟JavaScript的function createSomething() {
var obj = {};
obj = Something.apply(obj, arguments) || obj;
obj.__proto__ = Something.prototype; //Object.setPrototypeOf(obj, Something.prototype);
return o;
}
关键字:
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
虽然其他方法是可行的,但它们过于复杂。在Clojure中,您通常会创建一个实例化类型/记录的函数,并将该函数用作实例化的机制。将其翻译为JavaScript:
//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)
通过采用这种方法,您可以避免使用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");
)都是vanilla,do-nothing构造函数。您只需传入参数并将它们分配给同名的属性。毛茸茸的细节进入构造函数(例如Person
)。
由于它们无论如何都是一种很好的实践,因此无需创建这些额外的构造函数。它们可以很方便,因为它们允许您具有几个具有不同细微差别的构造函数。
是的,我们可以,javascript更多的是person
性质。
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;
};
”创建一个对象时,我们创建的对象INHERITS new
(),但是如果我们使用getAge
来调用Actor,那么我们传递的是apply(...) or call(...)
的对象,但我们传递的对象"this"
继承自WON'T
除非,我们直接传递apply或调用Actor.prototype但是......“this”将指向“Actor.prototype”,this.name将写入:Actor.prototype
。因此影响使用Actor.prototype.name
since创建的所有其他对象,我们覆盖原型而不是实例
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()函数运行时,它执行Actor.call()
,因为“this”将指向this.name=name
,Actor.prototype
this.name=name; means Actor.prototype.name=name;
回到原始问题如何使用qazxsw poi,这是我的看法....
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
由于ES6可以通过Spread运算符实现,请参阅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回答的修订解决方案。
https://stackoverflow.com/a/42027742/7049810
如果您的环境支持function construct(constructor, args) {
function F() : void {
constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
,您可以像这样使用它
ECMA Script 2015's spread operator (...
)
注意:现在已发布ECMA Script 2015的规范并且大多数JavaScript引擎正在积极实现它,这将是执行此操作的首选方法。
您可以在几个主要环境function Something() {
// init stuff
}
function createSomething() {
return new Something(...arguments);
}
中查看Spread运营商的支持。
感谢帖子,我用这种方式:
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}
和实施:
var applyCtor = function(ctor, args)
{
var instance = new ctor();
ctor.prototype.constructor.apply(instance, args);
return instance;
};
假设你有一个Items构造函数,它会抛出你抛出的所有参数:
here
您可以使用Object.create()创建一个实例,然后使用该实例创建.apply():
function Items () {
this.elems = [].slice.call(arguments);
}
Items.prototype.sum = function () {
return this.elems.reduce(function (sum, x) { return sum + x }, 0);
};
从1 + 2 + 3 + 4 = 10开始运行时打印10:
var items = Object.create(Items.prototype);
Items.apply(items, [ 1, 2, 3, 4 ]);
console.log(items.sum());
在ES6中,$ node t.js
10
非常方便:
Reflect.construct()
@Matthew我认为最好修复构造函数属性。
Reflect.construct(F, args)
@ Matthew答案的改进版本。这个表单通过将temp类存储在一个闭包中获得了一些性能上的好处,以及一个函数可以用来创建任何类的灵活性
// 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;
}
这可以通过调用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;
}
}();
来使用
你可以将init的东西移到applyCtor(class, [arg1, arg2, argn]);
原型的单独方法中:
Something
这个答案有点晚了,但想到任何看到这个的人都可以使用它。有一种方法可以使用apply返回一个新对象。虽然它需要对您的对象声明进行一点改动。
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
对于在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;
更新:我已经改变了我的第一个例子来加总任意数量的参数,而不仅仅是两个。