paint-brush
Creación de objetos invocables en JavaScriptpor@arccoza
9,013 lecturas
9,013 lecturas

Creación de objetos invocables en JavaScript

por Adrien5m2019/12/07
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow
ES

Demasiado Largo; Para Leer

Un objeto invocable es una estructura de datos que se comporta tanto como un objeto como una función. Los objetos a los que se puede llamar también se pueden considerar como funciones con estado. No está integrado en el lenguaje como Python, pero hay varias formas de hacerlo funcionar. El principal obstáculo es dar a un objeto de función una referencia a sí mismo. Vamos a necesitar heredar del constructor de funciones, que hereda de Object. Queremos que esa función pueda acceder a su propio objeto y llamar a un método, pasando sus argumentos.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Creación de objetos invocables en JavaScript
Adrien HackerNoon profile picture
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
contexto

Si 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.

El reto

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.

Las soluciones

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
.

El camino de la vinculación

 '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

  • No se basa en características obsoletas o modernas.
  • No es necesario modificar prototipos.

Contras

  • Requiere envolver el objeto de función en una función enlazada.

El camino de Callee

 '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

  • Muy simple.
  • No es necesario modificar prototipos.

Contras

  •  arguments
    y
     arguments.callee
    no están disponibles en 'modo estricto', consulte MDN para obtener más información.
  • El código moderno debe evitar
     arguments
    para el operadorspread / rest .

La forma de cierre y prototipo

 '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

  • No requiere envolver el objeto devuelto con un
     Proxy
    o
     bind
    .

Contras

  • Requiere modificar prototipos.
  • La modificación de prototipos es lenta y tiene otros efectos secundarios, consulte MDN .

El Camino Proxy

 '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

  • Forma sencilla y nativa de interceptar llamadas y redirigirlas.
  • No es necesario modificar prototipos.

Contras

  • Requiere envolver objetos creados por
     Callable
    en un
     Proxy
    .
  • Una pequeña penalización de rendimiento por usar
     Proxy
    manipuladores

Lo que aprendimos

JavaScript 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:

https://stackoverflow.com/questions/36871299/how-to-extend-function-with-es6-classes/40878674#40878674