将.apply()与’new’运算符配合使用。这可能吗?

2020/09/26 16:21 · javascript ·  · 0评论

在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接线员通话但是,人们提出了许多非常有趣的解决方案。

我更喜欢的解决方案是Matthew 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]));

这和基于评估的解决方案是唯一始终有效的解决方案,即使对于特殊的构造函数,如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关键字reset 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,我们将得到如上所述的最终结果。

这里的一个一般化的解决方案,可以调用任何构造(除了不同行为的时称作函数,如天然构造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中使用它的人,因为TS如果F返回任何内容,则会出错

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

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

function Something() {
    // init stuff
}

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

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

您可以在几个主要的环境检查的传播运营商的支持,在这里

假设您有一个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

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

Reflect.construct(F, args)

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

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

@Matthew答案的改进版本。通过将temp类存储在闭包中,此格式具有轻微的性能优势,并且具有一个可用于创建任何类的函数的灵活性

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

是的,这有点丑陋,但可以解决问题,而且非常简单。

如果您对基于评估的解决方案感兴趣

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但是如果您支持较旧的浏览器,则可以始终填充它。请参阅此处的MDN支持表

这是一个JSBin,可通过控制台输出查看它的运行情况

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

输出

演示{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在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,则该代码将无效。不过不太可能,请参见兼容性表

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

尽管其他方法可行,但它们过于复杂。在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)。

不必创建这些额外的构造函数,因为无论如何它们都是一个好习惯。它们很方便,因为它们允许您潜在地拥有多个具有不同细微差别的构造函数。

F()令人震惊的是,如何通过使用arguments.calleeaka创建者/工厂函数本身解决重用临时构造函数的问题: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

这是我的版本createSomething

function createSomething() {
    var obj = {};
    obj = Something.apply(obj, arguments) || obj;
    obj.__proto__ = Something.prototype; //Object.setPrototypeOf(obj, Something.prototype); 
    return o;
}

基于此,我尝试模拟newJavaScript 关键字:

//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创建对象时,然后创建的对象INHERITS getAge(),但是如果我们过去曾经apply(...) or call(...)调用过Actor,那么我们将传递一个对象,"this"但是传递的对象WON'T继承自Actor.prototype

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

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

由于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();
};
本文地址:http://javascript.askforanswer.com/jiang-applyyunewyunsuanfupeiheshiyongzhekenengma.html
文章标签: ,   ,   ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!