ES6 permite ampliar objetos especiales. Entonces es posible heredar de la función. Dicho objeto se puede llamar como una función, pero ¿cómo puedo implementar la lógica para dicha llamada?
class Smth extends Function {
constructor (x) {
// What should be done here
super();
}
}
(new Smth(256))() // to get 256 at this call?
Cualquier método de clase obtiene referencia a la instancia de clase a través de this
. Pero cuando se llama como función, se this
refiere a window
. ¿Cómo puedo obtener la referencia a la instancia de la clase cuando se la llama como función?
super(x)
(es decir, pasarlo aFunction
)? SinFunction
embargo, no estoy seguro de si realmente se puede extender.Error
, entre otros.Function
es simplemente un constructor de funciones. La implementación de la función debe pasarse al constructor. Si no deseaSmth
aceptar una implementación, debe proporcionarla en el constructor, es decirsuper('function implementation here')
.Function
constructor (tiempo de ejecución) que es muy diferente de una expresión de función (sintaxis).Respuestas:
La
super
llamada invocará alFunction
constructor, que espera una cadena de código. Si desea acceder a los datos de su instancia, simplemente puede codificarlos:pero eso no es realmente satisfactorio. Queremos usar un cierre.
Hacer que la función devuelta sea un cierre que pueda acceder a las variables de su instancia es posible, pero no fácil. Lo bueno es que no tiene que llamar
super
si no lo desea; aún puede obtenerreturn
objetos arbitrarios de los constructores de su clase ES6. En este caso, haríamosPero podemos hacerlo aún mejor y abstraer esto de
Smth
:Es cierto que esto crea un nivel adicional de indirección en la cadena de herencia, pero eso no es necesariamente algo malo (puede extenderlo en lugar del nativo
Function
). Si quieres evitarlo, usapero
Smth
tenga en cuenta que no heredará dinámicamenteFunction
propiedades estáticas .fuente
Smth
instanciasinstanceof Smth
(como todos esperarían). Puede omitir laObject.setPrototypeOf
llamada si no necesita este o ninguno de sus métodos prototipo declarado en su clase.Object.setPrototypeOf
es un gran peligro de optimización siempre que se haga justo después de crear el objeto. Es solo que si mutas el [[prototipo]] de un objeto de un lado a otro durante su vida útil, será malo.this
yreturn
un objeto.Este es un enfoque para crear objetos invocables que hagan referencia correctamente a sus miembros de objeto y mantengan la herencia correcta, sin interferir con los prototipos.
Simplemente:
Amplíe esta clase y agregue un
__call__
método, más abajo ...Una explicación en código y comentarios:
Ver en repl.it
Más explicación de
bind
:function.bind()
funciona de manera muy similarfunction.call()
, y comparten una firma de método similar:fn.call(this, arg1, arg2, arg3, ...);
más sobre mdnfn.bind(this, arg1, arg2, arg3, ...);
más sobre mdnEn ambos, el primer argumento redefine el
this
contexto dentro de la función. Los argumentos adicionales también se pueden vincular a un valor. Pero dondecall
inmediatamente llama a la función con los valores enlazados,bind
devuelve un objeto de función "exótico" que envuelve de forma transparente el original, conthis
y cualquier argumento preestablecido.Entonces, cuando define una función,
bind
algunos de sus argumentos:Llamas a la función vinculada solo con los argumentos restantes, su contexto está preestablecido, en este caso a
['hello']
.fuente
bind
funciona (es decir, por qué devuelve una instancia deExFunc
)?bind
devuelve un objeto de función transparente que envuelve el objeto de función al que fue llamado, que es nuestro objeto invocable, solo con elthis
rebote de contexto. Así que realmente devuelve una instancia deExFunc
. Publicación actualizada con más información sobrebind
.constructor
afterbind
inExFunc
. En las subclases de ExFunc, todos los miembros son accesibles. En cuanto ainstanceof
; en es6, las funciones vinculadas se denominan exóticas, por lo que su funcionamiento interno no es evidente, pero creo que pasa la llamada a su destino envuelto, a través deSymbol.hasInstance
. Es muy parecido a un Proxy, pero es un método simple para lograr el efecto deseado. Su firma es similar, no la misma.__call__
no puedo accederthis.a
othis.ab()
. por ejemplo, repl.it/repls/FelineFinishedDesktopenvironmentPuede envolver la instancia de Smth en un Proxy con una
apply
(y tal vezconstruct
) trampa:fuente
this
sigue siendo una función vacía (comprobarnew Smth().toString()
).setPrototypeOf
acerca de los proxies y no dice nada sobre ellos. Pero supongo que los proxies pueden ser tan problemáticos comosetPrototypeOf
. Y aproximadamentetoString
, se puede sombrear con un método personalizado enSmth.prototype
. El nativo depende de la implementación de todos modos.construct
trampa para especificar el comportamiento denew new Smth(256)()
. Y agregue métodos personalizados que sigan a los nativos que acceden al código de una función, comotoString
señaló Bergi.apply
método está implementado de la manera en que se supone que debe usarse, o es solo una demostración y necesito revisar más informaciónProxy
yReflect
usarlo de manera adecuada?Seguí el consejo de la respuesta de Bergi y lo envolví en un módulo de NPM .
fuente
Actualizar:
Desafortunadamente, esto no funciona del todo porque ahora está devolviendo un objeto de función en lugar de una clase, por lo que parece que esto en realidad no se puede hacer sin modificar el prototipo. Cojo.
Básicamente, el problema es que no hay forma de establecer el
this
valor para elFunction
constructor. La única forma de hacer esto realmente sería usar el.bind
método después, sin embargo, esto no es muy amigable con la clase.Podríamos hacer esto en una clase base auxiliar, sin
this
embargo, no estará disponible hasta después de lasuper
llamada inicial , por lo que es un poco complicado.Ejemplo de trabajo:
(El ejemplo requiere un navegador moderno o
node --harmony
.)Básicamente, la función base
ClassFunction
amplía envolverá laFunction
llamada al constructor con una función personalizada que es similar a.bind
, pero permite el enlace más tarde, en la primera llamada. Luego, en elClassFunction
propio constructor, llama a la función devuelta de lasuper
que ahora es la función vinculada, pasandothis
para terminar de configurar la función de vinculación personalizada.Todo esto es un poco complicado, pero evita la mutación del prototipo, que se considera de mala forma por razones de optimización y puede generar advertencias en las consolas de los navegadores.
fuente
bound
se referirá a la función que ustedreturn
de esa clase anónima. Simplemente nómbrelo y consúltelo directamente. También recomendaría evitar pasar cadenas de código, son un desastre para trabajar (en cada paso del proceso de desarrollo).extends
realmente no parece funcionar como se esperaba, ya queFunction.isPrototypeOf(Smth)
tambiénnew Smth instanceof Function
es falso.console.log((new Smth) instanceof Function);
estrue
para mí en Node v5.11.0 y el último Firefox.new Smth instanceof Smth
que no funciona con tu solución. Además, no habrá métodosSmth
disponibles en sus instancias, ya que solo devuelve un estándarFunction
, no unSmth
.extend Function
también hacenew Smth instanceof Smth
falso.Primero llegué a la solución
arguments.callee
, pero fue horrible.Esperaba que se rompiera en modo estricto global, pero parece que funciona incluso allí.
Fue una mala forma por usar
arguments.callee
, pasar el código como una cadena y forzar su ejecución en modo no estricto. Peroapply
apareció la idea de anular .Y la prueba, que muestra que puedo ejecutar esto como función de diferentes maneras:
Versión con
de hecho contiene
bind
funcionalidad:Versión con
hace
call
yapply
enwindow
inconsistente:por lo que el cheque debe trasladarse a
apply
:fuente
this
es ventana, no indefinida, por lo que la función creada no está en modo estricto (al menos en Chrome).Function
no está en modo estricto. Aunque horrible, es interesante +1. Sin embargo, probablemente no puedas caminar más en cadena.Esta es la solución que he encontrado que sirve a todas mis necesidades de ampliación de funciones y me ha servido bastante bien. Los beneficios de esta técnica son:
ExtensibleFunction
, el código es idiomático para extender cualquier clase ES6 (no, jugar con constructores o proxies fingidos).instanceof
/.constructor
devuelve los valores esperados..bind()
.apply()
y.call()
todos funcionan como se esperaba. Esto se hace anulando estos métodos para alterar el contexto de la función "interna" en oposición a laExtensibleFunction
instancia (o su subclase)..bind()
devuelve una nueva instancia del constructor de funciones (ya seaExtensibleFunction
una subclase). Se utilizaObject.assign()
para garantizar que las propiedades almacenadas en la función vinculada sean coherentes con las de la función de origen.Symbol
, que puede ser ofuscado por módulos o un IIFE (o cualquier otra técnica común de privatización de referencias).Y sin más preámbulos, el código:
Editar
Como estaba de humor, pensé que publicaría un paquete para esto en npm.
fuente
Hay una solución simple que aprovecha las capacidades funcionales de JavaScript: pase la "lógica" como un argumento de función al constructor de su clase, asigne los métodos de esa clase a esa función, luego devuelva esa función del constructor como resultado :
Lo anterior se probó en Node.js 8.
Una deficiencia del ejemplo anterior es que no admite métodos heredados de la cadena de superclase. Para apoyar eso, simplemente reemplace "Object. GetOwnPropertyNames (...)" con algo que devuelva también los nombres de los métodos heredados. Creo que cómo hacer eso se explica en alguna otra pregunta-respuesta en Stack Overflow :-). Por cierto. Sería bueno si ES7 agregara un método para producir nombres de métodos heredados también ;-).
Si necesita admitir métodos heredados, una posibilidad es agregar un método estático a la clase anterior que devuelve todos los nombres de métodos heredados y locales. Luego llámalo desde el constructor. Si luego extiendes esa clase Funk, también obtienes ese método estático heredado.
fuente