Las funciones son una de las bases de cualquier aplicación escrita en JavaScript. Se utilizan para construir capas de abstracción, para construir componentes que actúan como clases, para ocultar información (ocultación de información) y para actuar también como módulos. Aunque las clases, los espacios de nombres y las unidades existen en TypeScript, las funciones siguen desempeñando el papel principal en la descripción de cómo se hacen las cosas. TypeScript también agrega algunas características nuevas a las funciones estándar de JavaScript para que sea más fácil trabajar con ellas.
Funciones
Las funciones se pueden crear inicialmente en TypeScript como en JavaScript, las funciones se pueden crear como una función con nombre o una función anónima. Esto le permite elegir la forma más conveniente para usted en su aplicación, ya sea que esté creando varias funciones en una API o una función de un solo uso para pasar a otra función (el nombre de la función no importa en este caso).
Como un simple recordatorio, aquí están ambos métodos en JavaScript:
// función nombrada function add(x, y) { return x + y; } // función anónima let myAdd = function(x, y) { return x + y; };
Como en JavaScript, las funciones pueden referirse a variables fuera del cuerpo de la función. Cuando hacemos esto, estamos diciendo que estamos «capturando» estas variables. Si bien está fuera de los objetivos de esta guía comprender cómo funciona la captura y sus inconvenientes, comprender cómo funciona es importante para los programadores de JavaScript y TypeScript.
let z = 100; function addToZ(x, y) { return x + y + z; }
Tipos de funciones
Adición a tipos de funciones
Agreguemos los tipos a los dos ejemplos anteriores:
function add(x: number, y: number): number { return x + y; } let myAdd = function(x: number, y: number): number { return x + y; };
Podemos agregar los tipos para cada parámetro y luego a la función misma para agregar el tipo de retorno. TypeScript puede inferir el tipo de devolución strings mirando return
, por lo que a menudo se puede prescindir de él.
Escribir el tipo de función
Después de agregar los tipos de parámetros y el tipo de retorno a la función, escribamos el tipo de función completo observando cada parte del tipo de función:
let myAdd: (x: number, y: number) => number = function(x: number, y: number): number { return x + y; };
Los tipos de función tienen las mismas dos partes: el tipo de operandos y el tipo de valor devuelto. Se requieren ambas partes al escribir el tipo completo de función. Escribimos los tipos de parámetros como lo hacemos para las listas de parámetros, dando a cada parámetro un nombre y un tipo. Este nombre solo ayuda con la legibilidad del código y los nombres no tienen que ser idénticos, ya que podríamos haber escrito el código anterior de la siguiente manera:
let myAdd: (baseValue: number, increment: number) => number = function(x: number, y: number): number { return x + y; };
Siempre que los tipos de parámetros estén organizados correctamente, es un tipo válido de la función, independientemente de los nombres de los parámetros en el tipo de función.
La segunda parte es del tipo recursivo. La parte del tipo de devolución se ilustra con la flecha =>
para
separar los parámetros del tipo de devolución. Esta parte, como se mencionó anteriormente, se requiere en el tipo de la función, por lo que si la función no tiene valor de retorno, use el tipo void
en lugar de dejarlo.
Tenga en cuenta también que solo los operandos y el tipo de retorno constituyen el tipo de una función. Las variables capturadas no están en tipo. Entonces, las variables capturadas son parte del «estado oculto» de cualquier función y no parte de su API.
Tipo de inferencia
El compilador de TypeScript puede inferir el tipo si especifica el tipo en un lado y no en el otro:
// El compilador conoce el tipo completo de la función incluso si no se especifica en el lado izquierdo let myAdd = function(x: number, y: number): number { return x + y; }; // Aquí también, el tipo de función completo se define desde el lado izquierdo de la declaración let myAdd: (baseValue: number, increment: number) => number = function(x, y) { return x + y; };
Esto se llama escritura contextual, y es una forma de inferencia de tipos, esto ayuda a reducir el esfuerzo que se necesita para agregar tipos a los programas.
Parámetros predeterminados y opcionales
Se requiere pasar valores para todos los parámetros de una función para que su llamada funcione correctamente en TypeScript. Pero esto no significa que no se pueda pasar el valor null
o undefined
, significa que el compilador verificará que el usuario haya pasado un valor para cada parámetro de la función. El compilador también considera que estos parámetros son los únicos parámetros que se pasarán a la función. En otras palabras, la cantidad de valores pasados a la función debe coincidir con la cantidad de operandos que espera la función.
function buildName(firstName: string, lastName: string) { return firstName + " " + lastName; } let result1 = buildName("Bob"); // Error, no hay suficientes parámetros let result2 = buildName("Bob", "Adams", "Sr."); // Error más transacciones de las esperadas let result3 = buildName("Bob", "Adams"); // el numero de transacciones es bueno
En JavaScript, todos los parámetros son opcionales y los usuarios pueden dejar algunos según corresponda. Su valor undefined
es cuando se deja y no se pasa. Podemos obtener esta función agregando el carácter char » ? «a los últimos parámetros que queremos que sean opcionales. Por ejemplo, supongamos que queremos que el parámetro lastName
del ejemplo anterior sea opcional:
function buildName(firstName: string, lastName?: string) { if (lastName) return firstName + " " + lastName; else return firstName; } let result1 = buildName("Bob"); // Funciona correctamente let result2 = buildName("Bob", "Adams", "Sr."); // Error, más transacciones de las esperadas let result3 = buildName("Bob", "Adams"); // el numero de transacciones es bueno
Los operandos opcionales siempre deben agregarse y estar después de los operandos solicitados. Si queremos que el operando sea firstName
opcional en lugar del operando lastName
, debemos cambiar el orden de los operandos en la función colocando el operando firstName
al final de la lista de operandos.
En TypeScript también podemos establecer un valor que el parámetro mantendrá si el usuario no pasa un valor diferente, o si el usuario le pasa undefined
. Se denominan parámetros inicializados por defecto. Tomemos el ejemplo anterior y demos al parámetro lastName
el valor "Smith"
.
function buildName(firstName: string, lastName = "Smith") { return firstName + " " + lastName; } let result1 = buildName("Bob"); // "Bob Smith" let result2 = buildName("Bob", undefined); // "Bob Smith" let result3 = buildName("Bob", "Adams", "Sr."); // Error más transacciones de las esperadas let result4 = buildName("Bob", "Adams"); // el numero de transacciones es bueno
De forma predeterminada, los operandos inicializados que están después de todos los operandos requeridos son opcionales y se pueden dejar para pasar un valor cuando se llama a la función como operandos opcionales. Esto significa que los parámetros opcionales y los parámetros predeterminados al final de la lista comparten sus tipos. Entonces cada una de las funciones
function buildName(firstName: string, lastName?: string) { // ... }
y la funcion
function buildName(firstName: string, lastName = "Smith") { // ... }
Comparten el mismo tipo (firstName: string, lastName?: string) => string
. De modo que desaparece el valor por defecto del parámetro en lastName
, manteniendo el hecho de que el parámetro es un parámetro opcional.
A diferencia de los parámetros opcionales normales, los parámetros configurados por defecto no necesitan ser posteriores a los parámetros solicitados. Si un parámetro se inicializa de manera predeterminada antes que un parámetro obligatorio, los usuarios deberán pasar el valor undefined
explícitamente para obtener el valor predeterminado. Por ejemplo, el ejemplo anterior podría escribirse solo firstName
:
function buildName(firstName = "Will", lastName: string) { return firstName + " " + lastName; } let result1 = buildName("Bob"); // Error, no hay suficientes parámetros let result2 = buildName("Bob", "Adams", "Sr."); // Error más transacciones de las esperadas let result3 = buildName("Bob", "Adams"); // "Bob Adams" let result4 = buildName(undefined, "Adams"); // "Will Adams"
Parámetros restantes
Los parámetros obligatorios, opcionales y predeterminados tienen una característica en común: solo tienen un parámetro a la vez. A veces, es posible que desee trabajar con varios parámetros como un grupo, o es posible que no sepa cuántos parámetros tomará una función. Puede trabajar con parámetros pasados directamente a través de la variable arguments
que está visible dentro del cuerpo de cada función.
En TypeScript puede combinar estos parámetros en una sola variable:
function buildName(firstName: string, ...restOfName: string[]) { return firstName + " " + restOfName.join(" "); } let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
Los parámetros restantes se tratan como un número ilimitado de parámetros opcionales. Al pasar valores a un parámetro residual, puede pasar cualquier número de valores y tampoco se puede pasar ningún valor. El compilador construirá una matriz que contiene los valores pasados, esta matriz se llamará con el nombre dado después de los tres puntos ( ...
) , lo que le permitirá acceder a estos valores dentro de la función.
Los tres puntos también se utilizan con los parámetros restantes del tipo de función:
function buildName(firstName: string, ...restOfName: string[]) { return firstName + " " + restOfName.join(" "); } let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
Palabra clave this
Aprender a usar el interruptor this
en JavaScript es una fase por la que todo desarrollador debe pasar. Los desarrolladores de TypeScript necesitan aprender cómo usar this
. TypeScript le alerta sobre usos inapropiados del mismo dependiendo de algunas tecnologías.
Funciones de palabra clave this y funciones flecha(arrow functions)
La palabra clave this
en JavaScript es una variable que se establece cuando se llama a la función. Esto lo convierte en una función poderosa y flexible, pero siempre necesita conocer el contexto en el que se ejecuta la función. Esto suele ser confuso, especialmente cuando se devuelve una función o se pasa una función como argumento. Veamos un ejemplo sencillo:
let deck = { suits: ["hearts", "spades", "clubs", "diamonds"], cards: Array(52), createCardPicker: function() { return function() { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return {suit: this.suits[pickedSuit], card: pickedCard % 13}; } } } let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); alert("card: " + pickedCard.card + " of " + pickedCard.suit);
Observe que createCardPicker
una función devuelve otra función. Si intentamos ejecutar el ejemplo, obtendremos un error en lugar del cuadro de alerta esperado. Esto se debe a que el this
utilizado dentro de la función generada se configurará createCardPicker
para que sea el valor window
en lugar del objeto deck
. Esto se debe a que llamamos a la función cardPicker()
forma independiente. Una llamada de sintaxis sin método de nivel superior como esta usaría el window
this
. (Tenga en cuenta que el valor de ‘ this
será undefined
‘ en lugar de ‘ window
en modo estricto strict
).
Podemos resolver este problema asegurándonos de que la función this
esté correctamente antes de devolver la función para su uso posterior. De esta manera, la variable en la función aún podrá acceder al objeto deck
original sin importar cómo se use la función más adelante. Para hacer esto, cambiaremos la expresión de la función para usar la función arrow que viene con ECMAScript 6. Las funciones de flecha capturan la variable this
donde se crea la función en lugar de donde se llama:
let deck = { suits: ["hearts", "spades", "clubs", "diamonds"], cards: Array(52), createCardPicker: function() { // Observe que la línea de abajo se ha convertido en una función de flecha, permitiéndonos Al recoger la variable aquí return () => { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return {suit: this.suits[pickedSuit], card: pickedCard % 13}; } } } let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); alert("card: " + pickedCard.card + " of " + pickedCard.suit);
Si pasa la bandera
al --noImplicitThis
. Te avisará que el this
que está en this.suits[pickedSuit]
es de tipo any
.
Actas this
Sigue siendo this.suits[pickedSuit]
any
amable por desgracia. Esto se debe a que la variable this
proviene de la expresión de la función dentro del objeto literal. Se puede pasar un parámetro this
explícito arreglar esto. Los operandos this
son parámetros espurios que aparecen primero en la lista de parámetros de la función:
function f(this: void) { // asegúrese de que `this` // no se pueda usar dentro de esta función }
Para agregar algunas interfaces a nuestro ejemplo anterior, agregaremos los tipos Card
y Deck
para que los tipos sean más claros y reutilizables:
interface Card { suit: string; card: number; } interface Deck { suits: string[]; cards: number[]; createCardPicker(this: Deck): () => Card; } let deck: Deck = { suits: ["hearts", "spades", "clubs", "diamonds"], cards: Array(52), // Tenga en cuenta que la función ahora establece explícitamente que su destinatario debe ser de tipo Deck createCardPicker: function(this: Deck) { return () => { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return {suit: this.suits[pickedSuit], card: pickedCard % 13}; } } } let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); alert("card: " + pickedCard.card + " of " + pickedCard.suit);
TypeScript ahora espera createCardPicker
ser llamado en un objeto de tipo Deck
. Esto significa que la variable this
ahora es de tipo Deck
, y su tipo ya no es de tipo any
, por lo que la bandera --noImplicitThis
no producirá ningún error.
Parámetros this en devoluciones de llamada
También pueden ocurrir errores al trabajar con funciones de devolución de llamada this
, al pasar funciones a una biblioteca a la que llama más tarde. La razón de esto es que la biblioteca que llama a su devolución de llamada la llamará como una función normal, por lo que la variable this contendrá el valor undefined
. También puede usar los operadores this
para evitar errores de devolución de llamada. Primero, el escritor de la biblioteca deberá anotar el tipo de devolución de llamada de la variable this
de la siguiente manera:
interface UIElement { addClickListener(onclick: (this: void, e: Event) => void): void; }
Aquí, la expresión this: void
significa que la función addClickListener
espera onclick
que sea una función que no requiere un tipo this
. En segundo lugar, agregue una nota al pie de página al código que llama a la función con la variable this
de la siguiente manera:
class Handler { info: string; onClickBad(this: Handler, e: Event) { // Falso, estamos usando this // aquí, por lo que llamar a esta devolución de llamada fallará this.info = e.message; } } let h = new Handler(); uiElement.addClickListener(h.onClickBad); // Error
Después de agregar una anotación de tipo a la variable this
, esto dice explícitamente que la función onClickBad
debe llamarse desde una instancia de la clase Handler
. TypeScript detectará entonces que addClickListener
requiere una función propietaria de la nota al pie this: void
. Para corregir este error, cambie el tipo this
de la siguiente manera:
class Handler { info: string; onClickGood(this: void, e: Event) { // La variable this no se puede utilizar aquí porque es de tipo void console.log('clicked!'); } } let h = new Handler(); uiElement.addClickListener(h.onClickGood);
Debido a que la función onClickGood
define this
su propio tipo como si fuera su propio tipo void
, se puede pasar a addClickListener
. Pero, por supuesto, esto significa que no podrá acceder this.info
. Si desea ambas funciones, tendrá que usar una función de flecha:
class Handler { info: string; onClickGood = (e: Event) => { this.info = e.message } }
Esto funciona porque las funciones de flecha no capturan la variable this
, por lo que siempre puede pasarla a lo que espera una variable this
de tipo void
( this: void
). La desventaja de este método es que la función de flecha se crea para cada objeto de tipo Handler
. Los métodos se crean una sola vez y se adjuntan a la cadena de prototipos del Handler
. Es compartido por todos los objetos de tipo Handler
.
Sobrecargas
JavaScript es bastante dinámico, y es común que una sola función de JavaScript devuelva diferentes tipos de objetos según la forma de los parámetros que se les pasan.
let suits = ["hearts", "spades", "clubs", "diamonds"]; function pickCard(x): any { // Comprobamos si estamos trabajando con un objeto o una matriz // Si es así, elegimos la carta del conjunto de cartas if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard; } // Si no, dejamos que el usuario escoja la carta else if (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 }; } } let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }]; let pickedCard1 = myDeck[pickCard(myDeck)]; alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15); alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
La función pickCard
aquí tiene dos tipos de datos pasados por el usuario. Si pasa un objeto que representa una baraja de cartas, la función seleccionará la carta. Si elige una carta, le diremos cuál eligió. Pero, ¿cómo se le puede describir esto a un verificador de especies?
La respuesta es agregar varios tipos de funciones a la misma función como una lista de sobrecargas. Esta lista es lo que usará el compilador para juzgar las llamadas a funciones. Vamos a crear una lista de sobrecarga que describa lo que acepta la función pickCard
y lo devolverá:
let suits = ["hearts", "spades", "clubs", "diamonds"]; function pickCard(x: {suit: string; card: number; }[]): number; function pickCard(x: number): {suit: string; card: number; }; function pickCard(x): any { // Comprobamos si estamos trabajando con un objeto o una matriz // Si es así, elegimos la carta del conjunto de cartas if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard; } // Si no, permitimos que el usuario elija la carta else if (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 }; } } let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }]; let pickedCard1 = myDeck[pickCard(myDeck)]; alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15); alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
Con este cambio, las sobrecargas ahora agregan verificación de tipos a pickCard
. Para que el compilador elija verificar los tipos correctamente, realiza un proceso similar al que hace JavaScript implícito. Mira la lista de sobrecargas y luego intenta llamar a la función con los parámetros dados con la primera sobrecarga, si hay una coincidencia, elegirá esa sobrecarga como verdadera.
Por ello, es habitual ordenar las sobrecargas desde las más específicas y detalladas hasta las menos precisas. Tenga en cuenta que la pieza function pickCard(x): any
no forma parte de la lista de sobrecargas, por lo que la función solo tiene dos sobrecargas: una que toma un objeto y la otra que toma un número. Llamar a pickCard
cualquier otro tipo de parámetro producirá un error.
Recursos
- Múltiples Constantes en TypeScript
- Iteradores y generadores en TypeScript
- Símbolo en TypeScript (Symbol)
- Tipos Avanzados en TypeScript
- Tipos de Compatibilidad en TypeScript
- Inferir Tipos en TypeScript
- Tipos Generalizados (Generics) en TypeScript
- Tipos Básicos de Datos en TypeScript
- Interfaces en TypeScript
- Declaración de Variables en TypeScript
- Funciones en TypeScript
- Categorías en TypeScript
- Introducción a TypeScript