Guía de JavaScript Parte 4: Características

Hoy publicamos la cuarta parte de la traducción del manual de JavaScript, dedicada a las funciones.

Parte 1: primer programa, características del lenguaje, estándares
Parte 2: estilo de código y estructura del programa
Parte 3: variables, tipos de datos, expresiones, objetos.
Parte 4: funciones
Parte 5: matrices y bucles
Parte 6: excepciones, punto y coma, literales comodín
Parte 7: modo estricto, esta palabra clave, eventos, módulos, cálculos matemáticos
Parte 8: Descripción general de las características de ES6
Parte 9: Descripción general de los estándares ES7, ES8 y ES9



Funciones de JavaScript


Hablemos sobre las funciones en JavaScript, hagamos una revisión general de ellas y consideremos los detalles sobre ellas, cuyo conocimiento le permitirá usarlas de manera efectiva.

Una función es un bloque de código independiente que, una vez declarado, se puede invocar tantas veces como sea necesario. Una función puede, aunque no es necesaria, aceptar parámetros. Las funciones devuelven un solo valor.

Las funciones en JavaScript son objetos, o más bien, son objetos de tipo Function . Su diferencia clave con los objetos ordinarios, dándoles las capacidades excepcionales que poseen, es que pueden llamarse funciones.

Además, las funciones en JavaScript se denominan "funciones de primera clase", ya que pueden asignarse a variables, pueden pasarse a otras funciones como argumentos y pueden devolverse desde otras funciones.

Primero, consideramos las características de trabajar con funciones y las construcciones sintácticas correspondientes que existían en el lenguaje antes del advenimiento del estándar ES6 y que aún son relevantes.

Así es como se ve una declaración de función.

 function doSomething(foo) { // - } 

En estos días, estas funciones se denominan "normales", distinguiéndolas de las funciones de "flecha" que aparecieron en ES6.

Puede asignar una función a una variable o constante. Tal construcción se llama expresión de función.

 const doSomething = function(foo) { // - } 

Puede notar que en el ejemplo anterior, la función se asigna a una constante, pero en sí misma no tiene un nombre. Dichas funciones se llaman anónimas. Se pueden asignar nombres a funciones similares. En este caso, estamos hablando de una expresión de función con nombre (expresión de función con nombre).

 const doSomething = function doSomFn(foo) { // - } 

El uso de tales expresiones aumenta la conveniencia de la depuración (en los mensajes de error donde se realiza el seguimiento de la pila, el nombre de la función es visible). El nombre de la función en una expresión funcional también puede ser necesario para que la función pueda llamarse a sí misma, lo cual es indispensable para implementar algoritmos recursivos.

En el estándar ES6, han aparecido funciones de flecha, que son especialmente convenientes de usar en forma de las llamadas "funciones en línea", como argumentos pasados ​​a otras funciones (devoluciones de llamada).

 const doSomething = foo => { // - } 

Las funciones de flecha, además del hecho de que las estructuras utilizadas para declararlas, son más compactas que el uso de funciones ordinarias, difieren de ellas en algunas características importantes, que discutiremos a continuación.

Parámetros de funciones


Los parámetros son variables que se establecen en la etapa de declarar una función y contendrán los valores que se le pasan (estos valores se denominan argumentos). Las funciones en JavaScript pueden no tener parámetros o tener uno o más parámetros.

 const doSomething = () => { // - } const doSomethingElse = foo => { // - } const doSomethingElseAgain = (foo, bar) => { // - } 

Aquí hay algunos ejemplos de funciones de flecha.

Comenzando con el estándar ES6, las funciones pueden tener los llamados "parámetros predeterminados".

 const doSomething = (foo = 1, bar = 'hey') => { // - } 

Representan valores estándar establecidos por los parámetros de las funciones si, cuando se llama, los valores de algunos parámetros no están establecidos. Por ejemplo, la función que se muestra arriba se puede llamar pasando ambos parámetros que recibe y por otros métodos.

 doSomething(3) doSomething() 

En ES8, ahora puede poner una coma después del último argumento de una función (esto se llama coma final). Esta característica le permite aumentar la conveniencia de editar código cuando usa sistemas de control de versiones durante el desarrollo del programa. Los detalles sobre esto se pueden encontrar aquí y aquí .

Los argumentos pasados ​​a las funciones se pueden representar como matrices. Para analizar estos argumentos, puede usar un operador que se parezca a tres puntos (este es el llamado "operador de extensión" u "operador de propagación"). Así es como se ve.

 const doSomething = (foo = 1, bar = 'hey') => { // - } const args = [2, 'ho!'] doSomething(...args) 

Si las funciones necesitan tomar muchos parámetros, recordar el orden de su secuencia puede ser difícil. En tales casos, se utilizan objetos con parámetros y oportunidades para la desestructuración de objetos ES6.

 const doSomething = ({ foo = 1, bar = 'hey' }) => { // - console.log(foo) // 2 console.log(bar) // 'ho!' } const args = { foo: 2, bar: 'ho!' } doSomething(args) 

Esta técnica permite, describiendo los parámetros en forma de propiedades del objeto y pasando la función al objeto, obtener el acceso de la función a los parámetros por sus nombres sin usar construcciones adicionales. Lea más sobre esta técnica aquí .

Valores devueltos de funciones


Todas las funciones devuelven un cierto valor. Si el comando return no se especifica explícitamente, la función volverá undefined .

 const doSomething = (foo = 1, bar = 'hey') => { // - } console.log(doSomething()) 

La ejecución de la función finaliza después de que se ejecuta todo el código que contiene o después de que se encuentra la palabra clave return en el código. Cuando se encuentra esta palabra clave en una función, su operación se completa y el control se transfiere al lugar desde donde se llamó a la función.

Si después de la palabra clave return especifica un cierto valor, este valor vuelve al lugar de la llamada a la función como resultado de la ejecución de esta función.

 const doSomething = () => { return 'test' } const result = doSomething() // result === 'test' 

Solo se puede devolver un valor de una función. Para poder devolver múltiples valores, puede devolverlos como un objeto usando un objeto literal o como una matriz, y cuando llame a una función, use la construcción de asignación destructiva. Los nombres de los parámetros se guardan. Al mismo tiempo, si necesita trabajar con un objeto o una matriz devuelta por una función, es decir, en forma de un objeto o una matriz, puede hacerlo sin una asignación destructiva.

 const doSomething = () => { return ['Roger', 6] } const [ name, age ] = doSomething() console.log(name, age) //Roger 6 

La construcción const [ name, age ] = doSomething() se puede leer de la siguiente manera: "declare las constantes de name y age y asígneles los valores de los elementos de la matriz que devolverá la función".
Así es como se ve lo mismo usando un objeto.

 const doSomething = () => { return {name: 'Roger', age: 6} } const { name, age } = doSomething() console.log(name, age) //Roger 6 

Funciones anidadas


Las funciones se pueden declarar dentro de otras funciones.

 const doSomething = () => { const doSomethingElse = () => {} doSomethingElse() return 'test' } doSomething() 

El alcance de una función anidada está limitado por una función externa; no se puede llamar desde afuera.

Métodos de objeto


Cuando las funciones se utilizan como propiedades de los objetos, dichas funciones se denominan métodos de objeto.

 const car = { brand: 'Ford', model: 'Fiesta', start: function() {   console.log(`Started`) } } car.start() 

Esta palabra clave


Si comparamos la flecha y las funciones ordinarias utilizadas como métodos de objetos, podemos encontrar su diferencia importante, que consiste en el significado de la palabra clave this . Considera un ejemplo.

 const car = { brand: 'Ford', model: 'Fiesta', start: function() {   console.log(`Started ${this.brand} ${this.model}`) }, stop: () => {   console.log(`Stopped ${this.brand} ${this.model}`) } } car.start() //Started Ford Fiesta car.stop() //Stopped undefined undefined 

Como puede ver, llamar al método start() conduce al resultado esperado, pero el método stop() obviamente no funciona correctamente.

Esto se debe al hecho de que la palabra clave this se comporta de manera diferente cuando se usa en flechas y funciones ordinarias. A saber, la this en la función de flecha contiene un enlace al contexto que incluye la función. En este caso, cuando se trata del navegador, este contexto es el objeto de la window .

Así es como se ve la ejecución de dicho código en la consola del navegador.

 const test = { fn: function() {   console.log(this) }, arrFn: () => {   console.log(this) } } test.fn() test.arrFn() 


Características de esta palabra clave en funciones convencionales y de flecha

Como puede ver, llamar a esto en una función regular significa llamar al objeto, y this en la función de flecha apunta a la window .

Todo esto significa que las funciones de flecha no son adecuadas para la función de objeto y métodos de constructor (si intenta utilizar la función de flecha como constructor, se lanzará un TypeError ).

Expresiones funcionales llamadas inmediatamente


La expresión de función invocada inmediatamente (IIFE) es una función que se llama automáticamente inmediatamente después de que se declara.

 ;(function () { console.log('executed') })() 

El punto y coma antes de IIFE es opcional, pero su uso le permite asegurarse contra errores asociados con la colocación automática de punto y coma.

En el ejemplo anterior, la palabra executed irá a la consola, después de lo cual IIFE saldrá. IIFE, al igual que otras funciones, puede devolver los resultados de su trabajo.

 const something = (function () { return 'IIFE' })() console.log(something) 

Después de ejecutar este sencillo ejemplo, la consola obtendrá la línea IIFE , que resultó ser something después de ejecutar la expresión de función llamada inmediatamente. Puede parecer que no hay un beneficio particular de tal diseño. Sin embargo, si en IIFE se realizan algunos cálculos complejos que deben hacerse solo una vez, después de lo cual los mecanismos correspondientes son innecesarios, la utilidad de IIFE es obvia. Es decir, con este enfoque, después de ejecutar IIFE, solo el resultado devuelto por la función estará disponible en el programa. Además, podemos recordar que las funciones pueden devolver otras funciones y objetos. Estamos hablando de cierres, hablaremos de ellos a continuación.

Actualización de funciones


Antes de ejecutar el código JavaScript, se reorganiza. Ya hablamos sobre el mecanismo de elevación para variables declaradas usando la palabra clave var . Un mecanismo similar funciona con funciones. Es decir, estamos hablando del hecho de que las declaraciones de funciones en el curso del procesamiento del código antes de su ejecución se mueven a la parte superior de su alcance. Como resultado, por ejemplo, resulta que puede llamar a la función antes de que se declare.

 doSomething() //did something function doSomething() { console.log('did something') } 

Si mueve una llamada de función para que vaya después de su declaración, nada cambiará.

Si, en una situación similar, se usa una expresión funcional, entonces un código similar arrojará un error.

 doSomething() //TypeError var doSomething = function () { console.log('did something') } 

En este caso, resulta que aunque la declaración de la variable doSomething eleva a la parte superior del alcance, esto no se aplica a la operación de asignación.
Si, en lugar de var , utiliza las palabras clave let o const en una situación similar, este código tampoco funcionará, sin embargo, el sistema mostrará un mensaje de error diferente ( ReferenceError lugar de TypeError ), porque al usar let y const , no se const declaraciones constantes y variables.

Funciones de flecha


Ahora hablaremos más sobre las funciones de flecha que ya hemos cumplido. Pueden considerarse una de las innovaciones más significativas del estándar ES6, difieren de las funciones ordinarias no solo en apariencia, sino también en su comportamiento. En estos días se usan extremadamente ampliamente. Quizás no haya un solo proyecto moderno en el que no se utilicen en la gran mayoría de los casos. Podemos decir que su apariencia cambió para siempre la apariencia del código JS y las características de su trabajo.

Desde un punto de vista puramente externo, la sintaxis para declarar funciones de flecha es más compacta que la sintaxis de funciones ordinarias. Aquí está la declaración de una función regular.

 const myFunction = function () { //... } 

Aquí está el anuncio de la función de flecha, que, en general, si no tiene en cuenta las características de las funciones de flecha, es similar a la anterior.

 const myFunction = () => { //... } 

Si el cuerpo de una función de flecha contiene solo un comando, cuyo resultado devuelve esta función, puede escribirse sin llaves y sin la palabra clave return . Por ejemplo, dicha función devuelve la suma de los argumentos que se le pasan.

 const myFunction = (a,b) => a + b console.log(myFunction(1,2)) //3 

Como puede ver, los parámetros de las funciones de flecha, como en el caso de las funciones ordinarias, se describen entre paréntesis. Además, si dicha función toma solo un parámetro, puede especificarse sin paréntesis. Por ejemplo, aquí hay una función que devuelve el resultado de dividir el número que le pasó por 2.

 const myFunction = a => a / 2 console.log(myFunction(8)) //4 

Como resultado, resulta que las funciones de flecha son muy convenientes de usar en situaciones donde se necesitan funciones pequeñas.

▍ Resultados implícitos de retorno de funciones


Ya hemos mencionado esta característica de las funciones de flecha, pero es tan importante que se debata con más detalle. Estamos hablando del hecho de que las funciones de flecha de una línea admiten el retorno implícito de los resultados de su trabajo. Un ejemplo de devolver un valor primitivo de una función de flecha de una sola línea que ya hemos visto. ¿Qué pasa si tal función debería devolver un objeto? En este caso, las llaves del literal del objeto pueden confundir al sistema, por lo que se utilizan paréntesis en el cuerpo de la función.

 const myFunction = () => ({value: 'test'}) const obj = myFunction() console.log(obj.value) //test 

▍ Palabra clave esto y funciones de flecha


Arriba, cuando miramos las características de this , comparamos las funciones regulares y de flecha. Esta sección pretende llamar su atención sobre la importancia de sus diferencias. La this , en sí misma, puede causar ciertas dificultades, ya que depende del contexto de la ejecución del código y de si el modo estricto está habilitado o no.

Como hemos visto, cuando se usa la this en un método de un objeto representado por una función regular, this apunta al objeto al que pertenece el método. En este caso, hablamos de vincular la palabra clave this a un valor que represente el contexto de la función. En particular, si se llama a una función como método de objeto, la palabra clave this está vinculada a este objeto.

En el caso de las funciones de flecha, resulta que el enlace this no se realiza en ellas; usan la this desde su alcance. Como resultado, no se recomienda su uso como métodos de objeto.

El mismo problema ocurre cuando se usan funciones como controladores de eventos para elementos DOM. Por ejemplo, el button elemento HTML button usa para describir botones. El evento click se click cuando un usuario hace clic en un botón. Para responder a este evento en el código, primero debe obtener un enlace al elemento correspondiente y luego asignarle un controlador de eventos de click como una función. Como tal controlador, puede usar tanto la función regular como la función de flecha. Pero, si en el controlador de eventos necesita acceder al elemento para el que se llama (es decir, a this ), la función de flecha no funcionará aquí, ya que this valor disponible apunta al objeto de window . Para probar esto en la práctica, cree una página HTML, cuyo código se muestra a continuación, y haga clic en los botones.

 <!DOCTYPE html> <html> <body>   <button id="fn">Function</button>   <button id="arrowFn">Arrow function</button>   <script>     const f = document.getElementById("fn")     f.addEventListener('click', function () {         alert(this === f)     })     const af = document.getElementById("arrowFn")     af.addEventListener('click', () => {         alert(this === window)     })   </script> </body> </html> 

En este caso, cuando haga clic en estos botones, aparecerán ventanas que contienen true . Sin embargo, en el controlador de eventos de click del botón con el identificador fn , se verifica la igualdad de this el botón, y en el botón con la arrowFn del identificador arrowFn la igualdad de this y el objeto de la window .

Como resultado, si necesita llamar a this en el controlador de eventos del elemento HTML, la función de flecha no funcionará para el diseño de dicho controlador.

Cortocircuitos


Los cierres son un concepto importante en JavaScript. De hecho, si escribiste funciones JS, entonces también usaste cierres. Los cierres se utilizan en algunos patrones de diseño, en el caso de que necesite organizar un control estricto del acceso a ciertos datos o funciones.

Cuando se llama a una función, tiene acceso a todo lo que está dentro del alcance de lo externo. Pero no hay acceso a lo que se declara dentro de la función. Es decir, si una variable (u otra función) se declaró en una función, no se puede acceder al código externo durante la ejecución de la función o después de la finalización de su trabajo. Sin embargo, si se devuelve otra función de la función, esta nueva función tendrá acceso a todo lo que se declaró en la función original. En este caso, todo esto estará oculto del código externo en el cierre.

Considera un ejemplo. Aquí hay una función que toma el nombre del perro y luego lo muestra en la consola.

 const bark = dog => { const say = `${dog} barked!` ;(() => console.log(say))() } bark(`Roger`) // Roger barked! 

El valor devuelto por esta función aún no nos interesa, el texto se muestra en la consola usando IIFE, que en este caso no juega un papel especial, sin embargo, esto nos ayudará a ver la conexión entre esta función y su variante, en la que, en lugar de llamar a una función que muestra texto a la consola, devolveremos esta función desde la función reescrita bark() .

 const prepareBark = dog => { const say = `${dog} barked!` return () => console.log(say) } const bark = prepareBark(`Roger`) bark() // Roger barked! 

El resultado del código en dos casos es el mismo. Pero en el segundo caso, lo que se transfirió a la función original cuando se llamó (el nombre del perro, Roger ) se almacena en el cierre, después de lo cual es utilizado por otra función devuelta desde el original.

Realicemos otro experimento: cree, utilizando la función original, dos nuevos para perros diferentes.

 const prepareBark = dog => { const say = `${dog} barked!` return () => {   console.log(say) } } const rogerBark = prepareBark(`Roger`) const sydBark = prepareBark(`Syd`) rogerBark() sydBark() 

Este código generará lo siguiente.

 Roger barked! Syd barked! 

Resulta que el valor de la constante say está vinculado a la función que se devuelve desde la función prepareBark() .

Tenga en cuenta que, por say , cuando llama a prepareBark() nuevamente, obtiene un nuevo valor, mientras que el valor registrado en say primera vez que prepareBark() llama a prepareBark() no cambia. El punto es que con cada llamada a esta función, se crea un nuevo cierre.

Resumen


Hoy hablamos sobre las funciones ordinarias y de flecha, sobre las características de su declaración y uso, sobre cómo se comporta this palabra clave en diferentes situaciones y sobre los cierres. La próxima vez hablaremos de matrices y bucles.

Estimados lectores! ¿Cómo te sientes acerca de las funciones de flecha en JavaScript?

Source: https://habr.com/ru/post/es430382/


All Articles