2012-05-15 7 views
10

Estaba buscando soluciones para llamar a constructores de Javascript con una cantidad arbitraria de argumentos, y encontré algunas buenas publicaciones de SO, lo que me hizo pensar que estas tres llamadas deberían funcionar de la misma manera. Sin embargo, al menos en rinoceronte y Node.js, no lo hacen:encuadernación/aplicación de constructores en JavaScript

1. f = Date.bind(Date, 2000,0,1) 
2. g = Date.bind.call(Date, 2000, 0, 1) 
3. h = Date.bind.apply(Date, [2000, 0, 1]) 

La primera de ellas tiene el resultado deseado:

print(new f()) //=> Sat Jan 01 2000 00:00:00 GMT-0500 (EST) 

Pero los otros dos no lo hacen:

print(new g()) //=> Thu Feb 01 1900 00:00:00 GMT-0500 (EST) 
print(new h()) //=> Wed Jun 01 1904 00:00:00 GMT-0400 (EST) 

Así que algo se ha perdido en algún lado. Pensamientos sobre qué? ¿Es simplemente una mala idea mezclar cosas como apply, bind y/o call con new?

+0

Todos fallan en IE 8 porque no hay 'Date.bind'. ;-) – RobG

+0

Me parece que este es un uso inapropiado de 'bind'. Se debe usar para crear objetos de función, pero las fechas son objetos Date, no funciones, y no pueden invocarse. Además, sin el uso de bind o call o apply, el valor * this * es * Date * de todos modos, entonces, ¿cuál es el punto? Por último, no estás llamando a Date como un constructor, sino como una función. – RobG

+0

Claro, las fechas son objetos de fecha, pero la fecha misma es una función, y es la fecha misma a la que intento vincularme. La fecha es una función, así que la enlace y obtengo una nueva función, que luego uso como constructor llamándola con 'new'. Como dije en la parte superior de la pregunta, el punto real es poder llamar a un constructor con una lista de argumentos determinados en tiempo de ejecución. –

Respuesta

23

El previously accepted answer era incorrecto. Puede usar bind, call y apply con constructores para crear nuevos constructores. El único problema en su prueba es que ha olvidado que bind.apply y bind.call están aplicando y llamando a bind, no al propio constructor , entonces di los argumentos equivocados.

f = Date.bind(null, 2000,0,1) 
g = Function.bind.call(Date, null, 2000, 0, 1) 
h = Function.bind.apply(Date, [ null, 2000, 0, 1 ]) 

new f() //=> Sat Jan 01 2000 00:00:00 GMT-0500 (EST) 
new g() //=> Sat Jan 01 2000 00:00:00 GMT-0500 (EST) 
new h() //=> Sat Jan 01 2000 00:00:00 GMT-0500 (EST) 

Los tres son instanceof Fecha.

Los argumentos de la llamada son el contexto de ejecución seguido de los argumentos a aplicar. Los argumentos de Apply son el contexto de ejecución y una matriz de argumentos. Los argumentos de Bind son el contexto de ejecución seguido de los argumentos a vincular.

Así que los argumentos para aplicar, por ejemplo, son el contexto para el que se aplicabind (Fecha) seguido de una matriz que es los argumentos para se unen (por lo que la primera miembro de la matriz es el contexto de la bind argumento). Es por eso que es confuso llamar o aplicar bind; se siente extraño proporcionar argumentos de contexto a ambos.

Tenga en cuenta que, al usar bind con constructores, el argumento de contexto siempre se ignora porque 'nuevo' crea explícitamente un nuevo contexto. Utilizo null cuando el argumento de contexto es irrelevante para mantenerlo claro, pero puede ser cualquier cosa.

Mientras tanto, solicite y llame a estos ejemplos si necesita saber que el contexto en el que se aplica/llame a bind es la función Fecha. Cambié 'Fecha' a 'Función' cuando sea posible para ayudar a iluminar lo que realmente está proporcionando el contexto en el que. Cuando llamamos a apply o llamamos a Date.bind, realmente llamamos a apply o call en el método de enlace no asociado al objeto Date. El método bind en tal caso podría provenir de cualquier función. Podría ser Number.bind.call (Date, null, 2000, 0, 1) y el resultado sería exactamente el mismo.

Si no es obvio por qué, tenga en cuenta la diferencia entre los siguientes ejemplos:

context.method(); 

y

var noLongerAMethod = context.method; 
noLongerAMethod(); 

En el segundo caso, el método se ha divorciado de su contexto original (.. .a menos que estuviera previamente enlazado) y se comportará de manera diferente si dependiera de 'esto' internamente. Cuando establecemos el enlace de una función como propiedad, en lugar de ejecutarlo directamente, es simplemente otro puntero al método de enlace genérico en Function.prototype.

Personalmente no creo que alguna vez haya tenido que llamar o aplicar bind, y es difícil imaginar una situación para la que sería una buena solución, pero obligar a los constructores a crear nuevos constructores es algo que he encontrado muy útil en ocasiones. En cualquier caso, es un rompecabezas divertido.

+0

Esta fue una respuesta muy útil. ¡Gracias! –

5

bind y apply/call sólo funciona invocación trabajo para función pero no constructor, así que básicamente con métodos nativos no se puede hacer esto, una forma es escribir un método bindConstruct pero puede implica una complejidad adicional:

function bindConstruct(fn) { 
    // since constructor always accepts a static this value 
    // so bindConstruct cannot specify this 
    var extraArgs = [].slice.call(arguments, 1); 

    // create a 'subclass' of fn 
    function sub() { 
     var args = extraArgs.concat([].slice.call(arguments)); 
     fn.apply(this, args); 
    } 
    sub.prototype = fn.prototype; 
    sub.prototype.constructor = sub; 

    return sub; 
} 

Esto, en realidad, crea una subclase en su constructor.

Luego, su código:

var MyClass = function(x, y) { 
    console.log(arguments); 
    console.log(x + y); 
} 
var BindedMyClass = bindConstruct(MyClass, 1, 2, 3); 
var c = new BindedMyClass(4, 5); 
console.log(c instanceof MyClass); 
console.log(c instanceof BindedMyClass); 

También puede escribir esta función para Function.prototype o como una extensión de la función nativa bind.

+1

Gracias, pero lamentablemente, esto tampoco funciona correctamente. Los métodos de fecha fallan en cualquier objeto que cree con mi nueva función de "Subclase de fecha" con 'TypeError: Método invocado en objeto incompatible '. Demostrando una vez más que Javascript realmente no tiene subclases. O clases en absoluto, para el caso ... –

+0

"vincular y aplicar/llamar solo funciona invocación de trabajo para funcionar pero no constructor" simplemente no es cierto; vea abajo. – Semicolon