![]() |
Portada del libro Clean JavaScript |
VAR, LET Y CONST
/*
Lo ideal es tratar de evitar a toda
costa el uso de var, ya que no permite
definir variables con ámbito de bloque,
lo cual puede derivar en comportamientos
inesperados.
La diferencia entre let y const radica
en que a const no se le puede reasignar
su valor, aunque si modificarlo.
*/
//
var___________________________________________________
var variable = 5;
{
console.log('inside',
variable); //
5
var variable = 10;
}
console.log('outside',
variable); //
10
variable = variable * 2;
console.log('changed',
variable); //
20
//
let___________________________________________________
let variable2 = 5;
{
console.log('inside',
variable2);
//
error Cannot access 'variable' before initialization
//
La variable2 vive dentro de este scope, por lo que no
//
Pede ser invocada anes de ser inicializada.
let variable2 = 10;
}
console.log('outside',
variable2); //
5
variable2 = variable2 * 2;
console.log('changed',
variable); //
10
// const_________________________________________________
const variable3 = 5;
variable3 = variable3 * 2;
//error
Ámbito global
Cualquier variable que no esté dentro de un
bloque de una función, estará dentro del ámbito global. Dichas variables serán
accesibles desde cualquier parte de la aplicación:
let greeting = 'hello
world';
function greet()
{
console.log(greeting);
}
greet();
Ámbito de bloque
Los bloques en Javascript se delimitan mediante llaves, una
de apertura ‘{‘, y otra de cierre ‘}’. Como comentamos en el apartado de “Uso
correcto de var, let y const”,
para definir variables con alcance de bloque debemos hacer uso de let o const:
{
let greeting = "Hello
universe";
var lang = "English";
console.log(greeting);
// Helo universe
}
console.log(lang);//”English”
console.log(greeting);////
Uncaught ReferenceError: greeting is not defined
como puedes ver, si invoco greeting desde fuera del bloque
me dice que la variable no está definida, porque esa variable solo existe
dentro del scope de las llaves, sin embargo si invoco lang si ya que al haver
usado la palabra reservada var y no let, esa variable vive fuera del scope.
Ámbito estático vs. Dinámico
El ámbito de las variables en JavaScript tiene un
comportamiento de naturaleza estática. Esto quiere decir que se determina en
tiempo de compilación en lugar de en tiempo de ejecución. Esto también se suele
denominar ámbito léxico (lexical scope). Veamos un ejemplo:
const number = 10;
function printNumber()
{
console.log(
number );
}
function app()
{
const number = 5;
printNumber();
}
app(); //
10
En el ejemplo, console.log (number) siempre imprimirá el
número 10 sin importar desde dónde se llame la función printNumber(). Si
JavaScript fuera un lenguaje con el ámbito dinámico, console.log(number)
imprimiría un valor diferente dependiendo de dónde se ejecutará la función
printNumber().
Hoisting
En JavaScript las declaraciones de las variables y funciones
se asignan en memoria en tiempo de compilación; a nivel práctico es como si el
intérprete moviera dichas declaraciones al principio de su ámbito. Este
comportamiento es conocido como hoisting.
Gracias al hoisting podríamos ejecutar una función
antes de su declaración:
greet(); //”Hello
world”;
function greet(){
let greeting = "Hello
world";
console.log(greeting);
}
Al asignar la declaración en memoria es como si “subiera” la
función al principio de su ámbito. En el caso de las variables, el hoisting
puede generar comportamientos inesperados, ya que como hemos dicho solo aplica
a la declaración y no a su asignación:
var grret = "Hi";
(function
() {
console.log(greet);
// Undefined
var greet = "Hello";
console.log(greet);
// Hello
})();
En el primer console.log del ejemplo, lo esperado es que
escriba “Hi”, pero como hemos comentado, el intérprete “eleva” la declaración
de la variable a la parte superior de su scope. Por lo tanto, el comportamiento
del ejemplo anterior sería equivalente a escribir el siguiente código:
var grret = "Hi";
(function
() {
var greet;
console.log(greet);//
"undefined"
greet = "Hello";
console.log(greet);
//”Hello”
})();
He usado este ejemplo porque creo que es muy ilustrativo
para explicar el concepto de hoisting, pero volver a declarar una variable con
el mismo nombre y además usar var para definirlas es muy mala idea.
Funciones
“Sabemos que estamos desarrollando código limpio cuando cada
función hace exactamente lo que su nombre indica”. – Ward Cunningham
Las funciones son la entidad
organizativa más básica en cualquier programa. Por ello, deben resultar
sencillas de leer y de entender, además de transmitir claramente su intención.
Antes de profundizar en cómo deberían ser, exploraremos las diferentes maneras
en las que se pueden definir: declaración, expresiones y funciones arrow.
Además, en esta última explicaremos el funcionamiento del objeto this, del cual
podemos adelantar que tiene un comportamiento poco intuitivo en JavaScript.
Declaración de una
función
function doSomething()
{
return "Doing
something";
}
doSomething()
//"Doing something"
Expresión de una función
Una expresión de una función
tiene una sintaxis similar a la declaración de una función, con la salvedad de
que asignamos la función a una variable:
const doSomething = function()
{
return "Doing
something";
}
doSomething()
//"Doing something"
Expresiones con
funciones flecha (arrow functions)
Con la aparición de ES6, se añadió al lenguaje la sintaxis
de la función flecha, una forma de definir funciones mucho más legible y
concisa.
const doSomething =()
=> "Doing
something";
//El
return está implícito si no añadimos las llaves.
Las arrow functions son ideales para declarar expresiones lambda1 (funciones en
línea), puesto que se reduce el ruido en la sintaxis y se mejora la
expresividad e intencionalidad del código.
//Sin
arrows functions
[1,
2, 3].map(function(n){return n * 2})
//Con
arrows functions
[1,
2, 3].map(n => n * 2)
Las arrow functions también son muy útiles a la hora de
escribirfunciones currificadas. Como sabrás, una función de este tipo toma un
argumento, devuelve una función que toma el siguiente argumento, y así
sucesivamente. Con las arrow functions, este proceso puede acortarse, lo que
permitirá obtener un código mucho más legible.
// Sin arrow functions
function add(x)
{
return function(y)
{
return x + y;
}
}
// Con arrow functions
const add = x => y => x + y;
const addTwo = add(2);
console.log(addTwo(5))
// 7
Funcionamiento del objeto this en
arrow functions
Otra característica interesante de las arrow functions es
que cambian el comportamiento por defecto del objeto this en JavaScript. Cuando
creamos una arrow function, su valor de this queda asociado permanentemente al
valor de this de su ámbito externo inmediato; window en el caso del ámbito
global del navegador o global en el caso del ámbito global de NodeJS:
const isWindow = () => this === window;
isWindow(); // true
En el caso de encontrarse en el ámbito local de un método,
el valor de this sería el valor del ámbito de la función. Veamos un ejemplo:
const counter =
{
number: 0,
increase()
{
setInterval(()=> ++this.number,
1000);
}
};
counter.increase();
Funciones autoejecutadas IIFE
Una forma de definir funciones menos conocida es a través de
las IIFE. Las IIFE (Immediately-Invoked Function Expressions), o funciones
autoejecutadas en castellano, son funciones que se ejecutan al momento de
definirse:
(function(){
//
do something
})()
Este patrón era muy utilizado para crear un ámbito de bloque
antes de que se introdujeran let y const. Desde la aparición de ES6 esto no
tiene demasiado sentido, pero es interesante conocerlo:
(function(){
var number = 42;
}());
console.log(number);
// Reference error
Cómo hemos visto en la sección de ámbito de bloque, el uso
de let y const para definirlo es mucho más intuitivo y conciso:
{
let number = 42;
}
console.log(number);
// Reference Error
Parámetros y argumentos
Los
argumentos son los valores con los que llamamos a las funciones, mientras que
los parámetros son las variables nombradas que reciben estos valores dentro de
nuestra función:
1 const double = x => x * 2; // x es el parámetro de
nuestra función
2 double(2); // 2 es el argumento con el que llamamos a
nuestra función
Limita el número de argumentos
Una recomendación importante es la de limitar el número de
argumentos que recibe una función. En general deberíamos limitarnos a tres
parámetros como máximo. En el caso de tener que exceder este número, podría ser
una buena idea añadir un nivel más de indirección a través de un objeto:
Parámetros por defecto
Desde ES6, JavaScript permite que los parámetros de la
función se inicialicen con valores por defecto.
// Con ES6
function greet(text = 'world')
{
console.log('Hello' + text);
}
greet(); //
sin parámetro. Hello world
greet(undefined);
// undefined. Hello world
greet('crafter')
// con parámetro. Hello crafter
En JavaScript clásico, para hacer algo tan sencillo como
esto teníamos que comprobar si el valor está sin definir y asignarle el valor
por defecto deseado:
// Antes de ES6
function greet(text)
{
if(typeof text === 'undefined')
text = 'world';
console.log('Hello
' + text);
}
Aunque no debemos abusar de los parámetros por defecto, esta
sintaxis nos puede ayudar a ser más concisos en algunos contextos.
Parámetro rest y operador spread
El operador … (tres puntos) es
conocido como el parámetro rest o como operador spread (propagación en
español), dependiendo de dónde se emplee. El parámetro rest unifica los
argumentos restantes en la llamada de una función cuando el número de
argumentos excede el número de parámetros declarados en esta. En cierta manera,
el parámetro rest actúa de forma contraria a spread (operador propagación en
español): mientras que spread “expande” los elementos de un array u objeto
dado, rest unifica un conjunto de elementos en un array.
//ESTAS DOS NUEVAS CARACTERÍSTICAS DE
ES6 SON MUY ÚTILES PARA EL TRABAJO CON FAMEWORKS REACTIVOS JS
//COMO ANGULAR, VUE Y REACT. EL
PARÁMETRO REST Y EL OPERADOR DE PROPAGACIÓN SPREAD.
/*- PARÁMETROS REST:
Los parámetros rest son
una forma de ir agregando virtualmente parámetros infinitos
a una función o dentro de
una variable. Por ejemplo tenemos un arreglo pero no sabemos
cuantas posiciones va a
tener, aqui definiremos un parámetro REST, para ello le anteponemos
tres puntos suspensivos.
antes de la variable u opbjeto donde van a estar guardados esos
posibles valores
infinitos.
*/
//Imaginemos una función que va a
recibir valores y los va a sumar pero
//no sabemos cuantos numeros vamos a
recibir para sumar.
function sumar
(a,b,
...c){
let resultado = a + b;
//c
lo convertimos en un sumatorio mediante un forEach y guardamos el resultado en
una variable
c.forEach(function(n){
resultado += n
});
//devolvemos esa variable
return resultado;
}
//console.log(sumar(1,2));
console.log(sumar(1,2,3));
//a partir del tercer numero se detecta
el spread y devuelve la suma de todos los números
console.log(sumar(1,2,3,4));
//SPREAD OPERATOR [OPERADOR DE
PROPAGACIÓN]
/*
Este operador nos va a permitir expandir
una expresión
en situaciones donde tengamos que
guardar múltiples
elementos o arguemntos.
ejemplo: Imaginemos que tenemos un
arreglo con cierto número
de elementos, pero en un momento dedo
recibimos nuevos
parámetros. En casos así en vez de estar
haciendo
concatenaciones o push a nuestro
arreglo, podemos
agregarlos con el SPREAD operator.
*/
const arr1 =
[1,2,3,4,5],
arr2 =
[6,7,8,9,0]
//console.log(arr1,
arr2);
//AHORA
IMAGINEMOS QUE QUEREMOS CREAR UN ARREGLO 3
//A
PARTIR DE DE ESTOS DOS ANTERIORES. YO PODRÍA HACER:
//const
arr3 =[arr1, arr2]; //<--SI YO HAGO ESTO ESTOY CREANDO UN ARREGLO DE DOS
POSICIONES QUE INTERNAMENTE TIENENDOS ARREGLOS DE 5 POSICIONES
//NO
ES LO QUE QUERÍAMOS, QUEREMOS CREAR UN ARRAY CON TANTAS POSICIONES COMO LA SUMA
DE LOS ANTERIORES Y SUS DATOS
//
console.log(arr3);
//PARA
ELLO ANTEPONGO LOS 3 PUNTOS SUSPENSIVOS SPREAD OPERATOR
const arr3 =[...arr1,
...arr2];
console.log(arr3);
//AHORA SI TENGO UN ARRAY CON 10 POSICIONES Y TODOS
LOS VALORES DE LOS ANTERIORES
Cláusulas de guarda
Las cláusulas de guarda, también conocidas como aserciones o
precondiciones, son una pieza de código que comprueba una serie de condiciones
antes de continuar con la ejecución de la función
En el siguiente ejemplo como puedes observar, tenemos
demasiados condicionales anidados.
const getPayAmount =
() => {
let result;
if
(isDead){
result = deadAmount();
}
else
{
if
(isSeparated){
result = separatedAmount();
}
else
{
if
(isRetired){
result = retiredAmount();
}
else{
result = normalPayAmount();
}
}
}
return result;
}
Para resolverlo
podríamos sustituir los casos edge por cláusulas de guarda
const getPayAmount =
() => {
if
(isDead)
return deadAmount();
if(isSeparated)
return separatedAmount();
if
(isRetired)
return retiredAmount();
return normalPayAmount();
}
Evita el uso de else
Otra estrategia que suelo utilizar para evitar la anidación
es no emplear la palabra clave else. En mis proyectos intento evitarlo siempre
que sea posible; de hecho, he trabajado en proyectos con miles de líneas de
código sin utilizar un solo else. Para lograrlo, suelo priorizar el estilo
declarativo, hacer uso de las cláusulas de guarda cuando uso estructuras
condicionales o reemplazo las estructurasif/else por el operador ternario, lo
que da lugar a un código mucho más comprensible y expresivo:
//if/else
const isRunning = true;
if(isRunning){
stop()
}
else{
run()
}
//operador ternario
isRunning ? stop()
: run()
Prioriza las condiciones asertivas
La evidencia nos dice que las frases afirmativas suelen ser
más fáciles de entender que las negativas, por esta razón deberíamos invertir,
siempre que sea posible, las condiciones negativas para convertirlas en
afirmativas:
// Negative
if(!canNotFormat)
{
format()
}
// Positive
if(canFormat)
{
format()
}
GLOSARIO
1 Expersiones Lambda en JavaScript
Las
expresiones lambda son conceptos que permiten tratar una función como datos.
Todo en JavaScript puede manejarse comoun objeto, lo que implica que una
función puede enviarse como un parámetro a otra función y recibirse como un
valor de retorno de la función llanada.
Ejemplo:
function learnLambda(array,
funct)
{
let output = "";
for
(const value of array)
{
output += funct(value)
+ " ";
}
console.log
(output);
}
const array =
[5, 6,
7, 8,
9];
const sqqr =
(value) => value ** 2;
learnLambda(array,
sqqr); //25
36 49 64 81
La
diferencia más notable entre Lambda y las funciones anónimas en JavaScript es
que las funciones Lambda se pueden nombrar.
En el
código anterior tenemos una función learnLambda(array,
funct), que recibe como parámetros un array y el resultado de una función,
en este caso la función felcha expresada y guardada en la constante sqqr.
2 Método Map
El
método map() de las instancias de Matriz crea una nueva matriz poblada con los
resultados de llamar a una función proporcionada en cada elemento de la matriz
llamada
// Método Map
const array1 =
[1, 4,
9, 16];
// Pass a function to map
const map1 = array1.map((x)
=> x * 2);
console.log(map1);
// [2, 8, 18, 32]
Sintaxis
map(callbackFn)
map(callbackFn, thisArg)
3 Método filter
El
método filter() de las instancias de Matriz crea una copia superficial de una
parte de una matriz dada, filtrada a sólo los elementos de la matriz dada que
pasan la prueba implementada por la función proporcionada.
No hay comentarios:
Publicar un comentario