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 thisrefiere 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)? SinFunctionembargo, no estoy seguro de si realmente se puede extender.Error, entre otros.Functiones simplemente un constructor de funciones. La implementación de la función debe pasarse al constructor. Si no deseaSmthaceptar una implementación, debe proporcionarla en el constructor, es decirsuper('function implementation here').Functionconstructor (tiempo de ejecución) que es muy diferente de una expresión de función (sintaxis).Respuestas:
La
superllamada invocará alFunctionconstructor, 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
supersi no lo desea; aún puede obtenerreturnobjetos 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
Smthtenga en cuenta que no heredará dinámicamenteFunctionpropiedades estáticas .fuente
Smthinstanciasinstanceof Smth(como todos esperarían). Puede omitir laObject.setPrototypeOfllamada si no necesita este o ninguno de sus métodos prototipo declarado en su clase.Object.setPrototypeOfes 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.thisyreturnun 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
thiscontexto dentro de la función. Los argumentos adicionales también se pueden vincular a un valor. Pero dondecallinmediatamente llama a la función con los valores enlazados,binddevuelve un objeto de función "exótico" que envuelve de forma transparente el original, conthisy cualquier argumento preestablecido.Entonces, cuando define una función,
bindalgunos de sus argumentos:Llamas a la función vinculada solo con los argumentos restantes, su contexto está preestablecido, en este caso a
['hello'].fuente
bindfunciona (es decir, por qué devuelve una instancia deExFunc)?binddevuelve un objeto de función transparente que envuelve el objeto de función al que fue llamado, que es nuestro objeto invocable, solo con elthisrebote de contexto. Así que realmente devuelve una instancia deExFunc. Publicación actualizada con más información sobrebind.constructorafterbindinExFunc. 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.aothis.ab(). por ejemplo, repl.it/repls/FelineFinishedDesktopenvironmentPuede envolver la instancia de Smth en un Proxy con una
apply(y tal vezconstruct) trampa:fuente
thissigue siendo una función vacía (comprobarnew Smth().toString()).setPrototypeOfacerca 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.constructtrampa 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, comotoStringseñaló Bergi.applymé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ónProxyyReflectusarlo 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
thisvalor para elFunctionconstructor. La única forma de hacer esto realmente sería usar el.bindmétodo después, sin embargo, esto no es muy amigable con la clase.Podríamos hacer esto en una clase base auxiliar, sin
thisembargo, no estará disponible hasta después de lasuperllamada 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
ClassFunctionamplía envolverá laFunctionllamada 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 elClassFunctionpropio constructor, llama a la función devuelta de lasuperque ahora es la función vinculada, pasandothispara 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
boundse referirá a la función que ustedreturnde 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).extendsrealmente no parece funcionar como se esperaba, ya queFunction.isPrototypeOf(Smth)tambiénnew Smth instanceof Functiones falso.console.log((new Smth) instanceof Function);estruepara mí en Node v5.11.0 y el último Firefox.new Smth instanceof Smthque no funciona con tu solución. Además, no habrá métodosSmthdisponibles en sus instancias, ya que solo devuelve un estándarFunction, no unSmth.extend Functiontambién hacenew Smth instanceof Smthfalso.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. Peroapplyapareció 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
bindfuncionalidad:Versión con
hace
callyapplyenwindowinconsistente:por lo que el cheque debe trasladarse a
apply:fuente
thises ventana, no indefinida, por lo que la función creada no está en modo estricto (al menos en Chrome).Functionno 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/.constructordevuelve 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 laExtensibleFunctioninstancia (o su subclase)..bind()devuelve una nueva instancia del constructor de funciones (ya seaExtensibleFunctionuna 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