var obj = new CallableObject(); obj(argumentos);
Un objeto invocable es una estructura de datos que se comporta tanto como un objeto como una función. Puede acceder y asignar propiedades
obj.bar
, métodos de llamada obj.foo()
, sino también llamar al objeto directamente obj()
, como si fuera una función.La llamada directa es como llamar a un método de
obj
que tiene acceso a las propiedades del objeto a través de su this
contextoSi tiene experiencia con Python, reconocerá esto, hay un protocolo de Python incorporado que usa el
__call__
método de clase. Cualquier método asignado a __call__
se puede acceder como obj.__call__()
o como obj()
.Los objetos invocables también se pueden considerar como funciones con estado. Las funciones son procedimientos inherentemente de instancia única y sin estado. Los objetos invocables son procedimientos instanciados y con estado.
En JavaScript casi todo es un objeto, incluidas las funciones, por lo que seguramente podemos hacer esto, pero ¿cómo? No está integrado en el lenguaje como Python, pero hay varias formas de hacerlo funcionar.
Inspirándonos en Python, queremos crear una Clase/constructor que podamos usar para crear objetos invocables que redirijan sus llamadas a un método llamado
_call
. Queremos esta redirección para que podamos heredar de nuestra Clase y anular y extender fácilmente el _call
método con nueva funcionalidad, sin tener que preocuparse por el funcionamiento interno del objeto invocable.Para hacer esto, necesitaremos heredar del constructor defunciones , que hereda de Object y nos permite crear tanto un objeto como una función dinámica.
Nuestro principal obstáculo es dar a un objeto de función una referencia a sí mismo.
Para tener una referencia a la
_call
método, la parte de la función de nuestro objeto de función, generada por nuestra clase / constructor Callable, debe tener una referencia a sí misma.Queremos crear un extensible
Callable
clase que mantiene una herencia adecuada y correcta en JavaScript, y nos permite llamar a los objetos que construye como funciones, con una referencia a ellos mismos, redirigiendo esas llamadas a un método anulable _call
. 'use strict' class Callable extends Function { constructor () { super ( '...args' , 'return this._bound._call(...args)' ) // Or without the spread/rest operator: // super('return this._bound._call.apply(this._bound, arguments)') this ._bound = this .bind( this ) return this ._bound } _call(...args) { console .log( this , args) } }
Pruébelo en repl.it
Debido a que heredamos deFunction , podemos crear funciones dinámicas a partir de cadenas, usando super en nuestro constructor. Así que la cadena a la que le pasamos
super
será el cuerpo de nuestra función. Queremos que esa función pueda acceder a su propio objeto y llamar a un método _call
, transmitiendo sus argumentos. Hacemos esto usando bind .El método bind nos permitirá establecer el
this
contexto de una función a lo que queramos, envolviendo esa función en una función de enlace transparente. Entonces unimos la función a sí misma con this.bind(this)
.Ahora nuestro objeto invocable tiene una referencia a sí mismo, excepto el objeto que devolvemos de nuestro constructor, devuelto por
this.bind
, es una versión envuelta de nuestro objeto original. Entonces, todas nuestras propiedades se adjuntarán a ese nuevo objeto de función envuelto, y nuestra función tiene una referencia al objeto antiguo pasado a bind
.La solución fácil a esto es adjuntar una referencia al nuevo objeto envuelto en el objeto anterior como
_bound
. Y el cuerpo de nuestra función, en la cadena pasada a super
, simplemente llama _call
utilizando el this._bound
referencia.ventajas
Contras
'use strict' class Callable extends Function { constructor () { super ( 'return arguments.callee._call.apply(arguments.callee, arguments)' ) // We can't use the rest operator because of the strict mode rules. // But we can use the spread operator instead of apply: // super('return arguments.callee._call(...arguments)') } _call(...args) { console .log( this , args) } }
Pruébelo en repl.it
Nuevamente usamos el
super
llamada para crear una función dinámica, pero esta vez obtenemos nuestra referencia a la función en sí aprovechando otra variable implícita dentro de una función, el objeto de argumentos .El objeto arguments tiene una propiedad arguments.callee que es una referencia a la función llamada. Usamos esta referencia como el primer argumento para
apply
que une las funciones this
contexto a sí mismo.Entonces, dentro del cuerpo de la función, la cadena pasó a super, solo necesitamos llamar al
_call
método en arguments.callee
.ventajas
Contras
arguments
y arguments.callee
no están disponibles en 'modo estricto', consulte MDN para obtener más información. arguments
para el operadorspread / rest . 'use strict' class Callable extends Function { constructor () { var closure = function ( ...args ) { return closure._call(...args) } // Or without the spread/rest operator: // var closure = function() { // return closure._call.apply(closure, arguments) // } return Object .setPrototypeOf(closure, new .target.prototype) } _call(...args) { console .log( this , args) } }
Pruébelo en repl.it
Aquí en lugar de crear una función dinámica con
super
, descartamos el objeto función creado por el constructor (el this
object ) y reemplácelo con un closure , devolviéndolo en lugar de this
desde el constructor
.El cierre también es un objeto de función, y puede referenciarse a sí mismo en su cuerpo a través del cerrado.
closure
variable. usamos el closure
referencia para redirigir llamadas a su método _call
.Pero hemos roto la cadena de prototipos reemplazando
this
con closure
, por lo que volvemos a adjuntar el prototipo del constructor a closure
usando Object.setPrototypeOf y new.target (que es una referencia al constructor) para obtener el prototipo.Puedes usar
this.constructor.prototype
en vez de new.target.prototype
, pero primero debes llamar super
para crear el this
objeto, que es un despilfarro.ventajas
Proxy
o bind
.Contras
'use strict' class Callable extends Function { constructor () { super () return new Proxy ( this , { apply : ( target, thisArg, args ) => target._call(...args) }) } _call(...args) { console .log( this , args) } }
Pruébelo en repl.it
Usando Proxy podemos interceptar llamadas a una función, usando el
apply
atrapar y redirigirlo a otra función. los apply
trampa le da a nuestro invocable una referencia a sí mismo como el target
argumento.Entonces creamos una Clase que hereda de Función,
Callable
, envolviendo los objetos invocables creados en un Proxy
, interceptando cualquier llamada realizada a esos objetos y redirigiéndolas al _call
método en el objeto mismo, usando el target
referencia.ventajas
Contras
Callable
en un Proxy
. Proxy
manipuladoresJavaScript es un lenguaje extraordinariamente flexible, y puede doblarlo en muchas formas interesantes, como Objetos invocables. Es posible que haya casos de uso para los Objetos invocables, pero en general no recomendaría usarlos, no es JavaScript idiomático y puede ser confuso o poco claro para cualquier otra persona que use su código. Esto fue principalmente un ejercicio interesante.
Referencias: