Búsqueda de sitios web

javascript - Heredar clase ES6/TS de una no-clase


Dado que la clase se extiende de no-clase (incluyendo, pero no limitado a, función),

function Fn() {} 

class Class extends Fn {
    constructor() {
        super();
    }
}

¿Cuáles son las consecuencias? ¿Qué dicen las especificaciones sobre eso?

Parece que las implementaciones actuales de Babel, Google V8 y Mozilla Spidermonkey están bien con eso, y TypeScript lanza un error

El tipo '() => void' no es un tipo de función constructora

Si este es un código válido de ES2015, ¿cuál es la manera correcta de manejarlo en TypeScript?

Respuesta #1

Parte de TypeScript

Hasta ahora, la especificación dice que una cláusula extends debe ser seguida por una TypeReference. Y una TypeReference debe estar en la forma de A.B.C<TypeArgument>, como MyModule.MyContainer<MyItem>. Por lo tanto, sintácticamente tu código es correcto. Pero no es el caso de tipado.

La especificación dice que la BaseClass debe ser una clase válida de TypeScript. Sin embargo, la especificación está desactualizada, como se indica aquí. Ahora TypeScript permite expresiones en la cláusula extends, siempre que las expresiones se calculen como una función constructora. La definición es, bueno, basada en la implementación. Puedes verlo aquí. En pocas palabras, una expresión puede considerarse como constructora si implementa la interfaz new() {}.

Parte ES2015

Entonces, tu problema es que la función simple no se reconoce como un constructor en TypeScript, lo cual es debatible porque la especificación ES2015 solo requiere que el objeto tenga un método interno [[construct]]. Mientras que el objeto de función definido por el usuario sí lo tiene.

ES2015 requiere que BaseClass sea un constructor en tiempo de ejecución. Un objeto esConstructor si tiene el método interno [[construct]]. La especificación dice que [[construct]] es un método interno para el Objeto Función. Las funciones de usuario son instancias de Objetos Función, por lo que naturalmente son constructores. Pero las funciones incorporadas y las funciones flecha pueden no tener [[construct]].

Por ejemplo, el siguiente código lanzará en tiempo de ejecución un TypeError porque parseInt es una función incorporada y no tiene [[construct]]

new parseInt()
// TypeError: parseInt is not a constructor

Y de ECMAScript

Las funciones flecha son como las funciones integradas en que ambas carecen de .prototype y de cualquier método interno [[Construct]]. Así que new (() => {}) lanza un TypeError, pero por lo demás las flechas son como las funciones:

Como regla general, cualquier función sin prototype no se puede instanciar con new.

Solución alternativa

En resumen, no toda función es un constructor, TypeScript lo captura al requerir new() {}. Sin embargo, la función definida por el usuario es un constructor.

Para solucionar esto, la forma más fácil es declarar Fn como una variable y convertirla en constructor.

interface FnType {}
var Fn: {new(): FnType} = (function() {}) as any

class B extends Fn {}

Razonando la incompatibilidad

AVISO: No soy un colaborador principal de TypeScript, pero solo un fan de TS que tiene varios proyectos secundarios relacionados con TS. Así que esta sección es mi suposición personal.

TypeScript es un proyecto que se originó en 2012, cuando ES2015 aún se vislumbraba en la oscuridad. TypeScript no tenía una buena referencia para la semántica de las clases.

En aquel entonces, el objetivo principal de TypeScript era mantenerse compatible con ES3/5. Por lo tanto, usar new en una función es legal en TypeScript, porque también es legal en ES3/5. Al mismo tiempo, TypeScript también busca capturar errores de programación. extends una función podría ser un error porque la función podría no ser un constructor sensato (por ejemplo, una función únicamente para efectos secundarios). ¡extends ni siquiera existía en ES3/5! Por lo tanto, TypeScript podía definir libremente su propio uso de extends, haciendo que extends deba ir acompañado de una variable class. Esto hizo que TypeScript fuera más TypeSafe, mientras seguía siendo compatible con JavaScript.

Ahora, la especificación ES2015 está finalizada. ¡JavaScript también tiene una palabra clave extends! Luego surge la incompatibilidad. Hay esfuerzos para resolver la incompatibilidad. Sin embargo, todavía existen problemas. El tipo () => void o Function no debería ser extensible, como se indicó anteriormente debido a la función incorporada. El siguiente código fallará

var a: (x: string) => void = eval
new a('booom')

Por otro lado, si se introdujera un ConstructorInterface en TypeScript y cada literal de función lo implementara, surgiría una incompatibilidad hacia atrás. El siguiente código se compila ahora, pero no cuando se introdujo ConstructorInterface

var a = function (s) {}
a = parseInt // compile error because parseInt is not assignable to constructor

Por supuesto, el equipo de TS puede tener una solución que equilibre estas dos opciones. Pero esto no es una alta prioridad. Además, si salsa, el nombre en clave para JavaScript potenciado por TS, se implementa completamente, este problema se resolverá de manera natural.

Respuesta #2

¿Cuáles son las consecuencias? ¿Qué dicen las especificaciones sobre eso?

Es lo mismo que extender una clase de "EcmaScript 5". Declaras una función constructora y no hay prototipo en absoluto. Puedes extenderla sin ningún problema.

Pero para TypeScript, hay una gran diferencia entre function Fn() {} y class Fn {}. Ambos no son del mismo tipo.

El primero es solo una función que no devuelve nada (y TypeScript lo muestra con () => void). El segundo es una función constructora. TypeScript se niega a hacer un extends sobre una función que no es constructora.

Si algún día JavaScript se niega a hacer eso, romperá muchos códigos de JavaScript. Porque en este momento, function Fn() {} es la forma más utilizada de declarar una clase en JavaScript puro. Pero desde el punto de vista de TypeScript, esto no es "seguro en cuanto a tipos".

Creo que la única forma de TypeScript es usar una clase:

class Fn {} 

class Class extends Fn {
    constructor() {
        super();
    }
}

Artículos relacionados