Как я могу построить объект, используя массив значений для параметров, а не перечислять их в JavaScript? - PullRequest
11 голосов
/ 02 мая 2009

Возможно ли это? Я создаю одну базовую фабричную функцию для управления фабриками разных типов (но они имеют некоторое сходство), и я хочу иметь возможность передавать аргументы в виде массива базовой фабрике, которая затем, возможно, создает экземпляр нового объекта, заполняющего аргументы конструктор соответствующего класса через массив.

В JavaScript можно использовать массив для вызова функции с несколькими аргументами с помощью метода apply:

namespace.myFunc = function(arg1, arg2) { //do something; }
var result = namespace.myFunc("arg1","arg2");
//this is the same as above:
var r = [ "arg1","arg2" ];
var result = myFunc.apply(namespace, r);

Не похоже, что в любом случае можно создать экземпляр объекта с использованием apply, правда?

Что-то вроде (это не работает):

var instance = new MyClass.apply(namespace, r);

Ответы [ 5 ]

15 голосов
/ 02 мая 2009

Попробуйте это:

var instance = {};
MyClass.apply( instance, r);

Все, что делает ключевое слово "new", это передает новый объект конструктору, который затем становится переменной this внутри функции конструктора.

В зависимости от того, как был написан конструктор, вам может потребоваться сделать следующее:

var instance = {};
var returned = MyClass.apply( instance, args);
if( returned != null) {
    instance = returned;
}

Обновление: комментарий говорит, что это не работает, если есть прототип. Попробуйте это.

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

newApply( MyClass, args);
2 голосов
/ 19 октября 2009

Обратите внимание, что

  • new myClass()
    

    без аргументов может завершиться ошибкой, поскольку функция конструктора может полагаться на существование аргументов.

  • myClass.apply(something, args)
    

    завершится ошибкой во многих случаях, особенно если он вызывается для собственных классов, таких как Date или Number.

Я знаю, что "eval is evil", но в этом случае вы можете попробовать следующее:

function newApply(Cls, args) {
    var argsWrapper = [];
    for (var i = 0; i < args.length; i++) {
        argsWrapper.push('args[' + i + ']');
    }
    eval('var inst = new Cls(' + argsWrapper.join(',') + ');' );
    return inst;
}

Все просто.

(работает так же, как Instance.New в этом блоге )

1 голос
/ 02 мая 2009

Хаки - это хаки, но они, возможно, немного более элегантны, чем некоторые другие, поскольку синтаксис вызова будет аналогичен тому, что вы хотите, и вам вообще не нужно будет изменять исходные классы:

Function.prototype.build = function(parameterArray) {
    var functionNameResults = (/function (.{1,})\(/).exec(this.toString());
    var constructorName = (functionNameResults && functionNameResults.length > 1) ? functionNameResults[1] : "";
    var builtObject = null;
    if(constructorName != "") {
       var parameterNameValues = {}, parameterNames = [];
       for(var i = 0; i < parameterArray.length; i++) {
         var parameterName = ("p_" + i);
         parameterNameValues[parameterName] = parameterArray[i];
         parameterNames.push(("parameterNameValues." + parameterName));
       }
       builtObject = (new Function("parameterNameValues", "return new " + constructorName + "(" + parameterNames.join(",") + ");"))(parameterNameValues);
    }
    return builtObject;
};

Теперь вы можете сделать одно из следующих действий для создания объекта:

var instance1 = MyClass.build(["arg1","arg2"]);
var instance2 = new MyClass("arg1","arg2");

Конечно, некоторым может не понравиться изменение прототипа объекта Function, поэтому вы можете сделать это таким образом и использовать вместо этого функцию:

function build(constructorFunction, parameterArray) {
    var functionNameResults = (/function (.{1,})\(/).exec(constructorFunction.toString());
    var constructorName = (functionNameResults && functionNameResults.length > 1) ? functionNameResults[1] : "";
    var builtObject = null;
    if(constructorName != "") {
       var parameterNameValues = {}, parameterNames = [];
       for(var i = 0; i < parameterArray.length; i++) {
         var parameterName = ("p_" + i);
         parameterNameValues[parameterName] = parameterArray[i];
         parameterNames.push(("parameterNameValues." + parameterName));
       }
       builtObject = (new Function("parameterNameValues", "return new " + constructorName + "(" + parameterNames.join(",") + ");"))(parameterNameValues);
    }
    return builtObject;
};

И тогда вы бы назвали это так:

var instance1 = build(MyClass, ["arg1","arg2"]);

Итак, я надеюсь, что они кому-то пригодятся - они позволят вам оставить исходные функции конструктора в покое и получить то, что вам нужно, в одной простой строке кода (в отличие от двух строк, необходимых для выбранного в настоящее время решения / обходного пути). .

Обратная связь приветствуется и приветствуется.


ОБНОВЛЕНИЕ: Еще одна вещь, на которую следует обратить внимание - попробуйте создать экземпляры одного и того же типа с помощью этих различных методов, а затем проверить, совпадают ли их свойства конструктора, - вы можете захотеть, чтобы это было так, если вам когда-либо понадобится проверить тип объекта. То, что я имею в виду, лучше всего иллюстрируется следующим кодом:

function Person(firstName, lastName) {
   this.FirstName = firstName;
   this.LastName = lastName;
}

var p1 = new Person("John", "Doe");
var p2 = Person.build(["Sara", "Lee"]);

var areSameType = (p1.constructor == p2.constructor);

Попробуйте это с некоторыми другими хаки и посмотрите, что произойдет. В идеале вы хотите, чтобы они были одного типа.


CAVEAT: как отмечено в комментариях, это не будет работать для тех функций конструктора, которые созданы с использованием синтаксиса анонимной функции, т.е.

MyNamespace.SomeClass = function() { /*...*/ };

Если вы не создадите их так:

MyNamespace.SomeClass = function SomeClass() { /*...*/ };

Решение, которое я предоставил выше, может или не может быть полезным для вас, вам нужно точно понимать, что вы делаете, чтобы найти лучшее решение для ваших конкретных нужд, и вы должны быть в курсе того, что происходит в будущем мое решение "работа". Если вы не понимаете, как работает мое решение, потратьте время на его выяснение.


АЛЬТЕРНАТИВНОЕ РЕШЕНИЕ: не один, чтобы упускать из виду другие варианты, вот один из других способов, которым вы могли бы снять шкуру с этой кошки (с аналогичными предостережениями к вышеописанному подходу), этот более эзотерический:

function partial(func/*, 0..n args */) {
   var args = Array.prototype.slice.call(arguments, 1);
   return function() {
      var allArguments = args.concat(Array.prototype.slice.call(arguments));
      return func.apply(this, allArguments);
   };
}

Function.prototype.build = function(args) {
   var constructor = this;
   for(var i = 0; i < args.length; i++) {
      constructor = partial(constructor, args[i]);
   }
   constructor.prototype = this.prototype;
   var builtObject = new constructor();
   builtObject.constructor = this;
   return builtObject;
};

Наслаждайтесь!

0 голосов
/ 02 мая 2009

Одна из возможностей - заставить конструктор работать как обычный вызов функции.

function MyClass(arg1, arg2) {
    if (!(this instanceof MyClass)) {
        return new MyClass(arg1, arg2);
    }

    // normal constructor here
}

Условие оператора if будет истинным, если вы вызовете MyClass как обычную функцию (в том числе с call / apply, если аргумент this не является MyClass object).

Теперь все они эквивалентны:

new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);
0 голосов
/ 02 мая 2009

как насчет обходного пути?

function MyClass(arg1, arg2) {

    this.init = function(arg1, arg2){
        //if(arg1 and arg2 not null) do stuff with args
    }

    init(arg1, arg2);
}

Так как же вы можете:

var obj = new MyClass();
obj.apply(obj, args);
...