from - ¿Cómo puedo llamar a un constructor de JavaScript usando call o apply?
funcion call javascript (7)
Esta pregunta ya tiene una respuesta aquí:
- Uso de .apply () con el operador ''nuevo''. es posible? 35 respuestas
¿Cómo podría generalizar la función a continuación para tomar N argumentos? (¿Usar llamadas o postularse?)
¿Hay alguna manera programática de aplicar argumentos a ''nuevo''? No quiero que el constructor sea tratado como una función simple.
/**
* This higher level function takes a constructor and arguments
* and returns a function, which when called will return the
* lazily constructed value.
*
* All the arguments, except the first are pased to the constructor.
*
* @param {Function} constructor
*/
function conthunktor(Constructor) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
console.log(args);
if (args.length === 0) {
return new Constructor();
}
if (args.length === 1) {
return new Constructor(args[0]);
}
if (args.length === 2) {
return new Constructor(args[0], args[1]);
}
if (args.length === 3) {
return new Constructor(args[0], args[1], args[2]);
}
throw("too many arguments");
}
}
Prueba qUnit:
test("conthunktorTest", function() {
function MyConstructor(arg0, arg1) {
this.arg0 = arg0;
this.arg1 = arg1;
}
MyConstructor.prototype.toString = function() {
return this.arg0 + " " + this.arg1;
}
var thunk = conthunktor(MyConstructor, "hello", "world");
var my_object = thunk();
deepEqual(my_object.toString(), "hello world");
});
Así es como lo haces:
function applyToConstructor(constructor, argArray) {
var args = [null].concat(argArray);
var factoryFunction = constructor.bind.apply(constructor, args);
return new factoryFunction();
}
var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);
La llamada es un poco más fácil
function callConstructor(constructor) {
var factoryFunction = constructor.bind.apply(constructor, arguments);
return new factoryFunction();
}
var d = callConstructor(Date, 2008, 10, 8, 00, 16, 34, 254);
Puede usar cualquiera de estos para crear funciones de fábrica:
var dateFactory = applyToConstructor.bind(null, Date)
var d = dateFactory([2008, 10, 8, 00, 16, 34, 254]);
o
var dateFactory = callConstructor.bind(null, Date)
var d = dateFactory(2008, 10, 8, 00, 16, 34, 254);
Funcionará con cualquier constructor, no solo built-ins o constructores que puedan funcionar como funciones (como Date).
Sin embargo, requiere la función Ectascript 5 .bind. Las cuñas probablemente no funcionen correctamente.
Un enfoque diferente, más en el estilo de algunas de las otras respuestas, es crear una versión de función del built in new
. Esto no funcionará en todos los editores (como Fecha).
function neu(constructor) {
// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2
var instance = Object.create(constructor.prototype);
var result = constructor.apply(instance, Array.prototype.slice.call(arguments, 1));
// The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
return (result !== null && typeof result === ''object'') ? result : instance;
}
function Person(first, last) {this.first = first;this.last = last};
Person.prototype.hi = function(){console.log(this.first, this.last);};
var p = neu(Person, "Neo", "Anderson");
Y ahora, por supuesto, usted puede hacer .apply
o .call
o .bind
en neu
como es normal.
Por ejemplo:
var personFactory = neu.bind(null, Person);
var d = personFactory("Harry", "Potter");
Siento que la primera solución que doy es mejor, ya que no depende de que usted replique correctamente la semántica de un built-in y funciona correctamente con builtins.
En ECMAScript 6, puede usar el operador de difusión para aplicar un constructor con la nueva palabra clave a una matriz de argumentos:
var dateFields = [2014, 09, 20, 19, 31, 59, 999];
var date = new Date(...dateFields);
console.log(date); // Date 2014-10-20T15:01:59.999Z
Esta función es idéntica a new
en todos los casos. Sin embargo, probablemente sea significativamente más lento que la respuesta de 999, así que úsela solo si realmente la necesita.
function applyConstructor(ctor, args) {
var a = [];
for (var i = 0; i < args.length; i++)
a[i] = ''args['' + i + '']'';
return eval(''new ctor('' + a.join() + '')'');
}
ACTUALIZACIÓN: Una vez que el soporte de ES6 esté extendido, podrás escribir esto:
function applyConstructor(ctor, args) {
return new ctor(...args);
}
... pero no será necesario, porque la función de biblioteca estándar Reflect.construct()
hace exactamente lo que está buscando.
Hay una solución reutilizable para este caso. Para cada clase a la que desee llamar con método apply o call, debe llamar antes a convertToAllowApply (''classNameInString''); la clase debe estar en el mismo scoope o scoope global (no intento enviar ns.className por ejemplo ...)
Hay el código:
function convertToAllowApply(kName){
var n = ''/n'', t = ''/t'';
var scrit =
''var oldKlass = '' + kName + '';'' + n +
kName + ''.prototype.__Creates__ = oldKlass;'' + n +
kName + '' = function(){'' + n +
t + ''if(!(this instanceof '' + kName + '')){''+ n +
t + t + ''obj = new '' + kName + '';''+ n +
t + t + kName + ''.prototype.__Creates__.apply(obj, arguments);''+ n +
t + t + ''return obj;'' + n +
t + ''}'' + n +
''}'' + n +
kName + ''.prototype = oldKlass.prototype;'';
var convert = new Function(scrit);
convert();
}
// USE CASE:
myKlass = function(){
this.data = Array.prototype.slice.call(arguments,0);
console.log(''this: '', this);
}
myKlass.prototype.prop = ''myName is myKlass'';
myKlass.prototype.method = function(){
console.log(this);
}
convertToAllowApply(''myKlass'');
var t1 = myKlass.apply(null, [1,2,3]);
console.log(''t1 is: '', t1);
Otro enfoque, que requiere modificar el constructor real al que se llama, pero me parece más limpio que utilizar eval () o introducir una nueva función ficticia en la cadena de construcción ... Mantenga su función conthunktor como
function conthunktor(Constructor) {
// Call the constructor
return Constructor.apply(null, Array.prototype.slice.call(arguments, 1));
}
Y modifique los constructores que se llaman ...
function MyConstructor(a, b, c) {
if(!(this instanceof MyConstructor)) {
return new MyConstructor(a, b, c);
}
this.a = a;
this.b = b;
this.c = c;
// The rest of your constructor...
}
Entonces puedes probar:
var myInstance = conthunktor(MyConstructor, 1, 2, 3);
var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6
Prueba esto:
function conthunktor(Constructor) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var Temp = function(){}, // temporary constructor
inst, ret; // other vars
// Give the Temp constructor the Constructor''s prototype
Temp.prototype = Constructor.prototype;
// Create a new instance
inst = new Temp;
// Call the original Constructor with the temp
// instance as its context (i.e. its ''this'' value)
ret = Constructor.apply(inst, args);
// If an object has been returned then return it otherwise
// return the original instance.
// (consistent with behaviour of the new operator)
return Object(ret) === ret ? ret : inst;
}
}
Usar un constructor temporal parece ser la mejor solución si Object.create
no está disponible.
Si Object.create
está disponible, usarlo es una opción mucho mejor. En Node.js, el uso de Object.create
produce un código mucho más rápido. Aquí hay un ejemplo de cómo se puede usar Object.create
:
function applyToConstructor(ctor, args) {
var new_obj = Object.create(ctor.prototype);
var ctor_ret = ctor.apply(new_obj, args);
// Some constructors return a value; make sure to use it!
return ctor_ret !== undefined ? ctor_ret: new_obj;
}
(Obviamente, el argumento args
es una lista de argumentos para aplicar).
Tenía un código que originalmente usaba eval
para leer un dato creado por otra herramienta. (Sí, eval
es malo.) Esto instanciaría un árbol de cientos a miles de elementos. Básicamente, el motor de JavaScript fue responsable de analizar y ejecutar un montón de new ...(...)
expresiones. Convertí mi sistema para analizar una estructura JSON, lo que significa que tengo que hacer que mi código determine qué constructor llamar para cada tipo de objeto en el árbol. Cuando ejecuté el nuevo código en mi suite de prueba, me sorprendió ver una disminución dramática relativa a la versión de eval
.
- Test suite con versión
eval
: 1 segundo. - Test suite con la versión JSON, usando el constructor temporal: 5 segundos.
- Test suite con versión JSON, utilizando
Object.create
: 1 segundo.
El conjunto de pruebas crea múltiples árboles. applytoConstructor
mi función applytoConstructor
se llamó unas 125,000 veces cuando se ejecuta el banco de pruebas.