
JavaScript es el mejor(?) lenguaje para desarrollo web, y aunque muy posiblemente también tendrás que aprender React
o Angular
o Vue
o alternativas, al final todo se trata de JavaScript
Introducción
Éste es el curso básico de JavaScript
, un paseo indispensable por el lenguaje que te servirá de punto de entrada a la programación con React
aplicada a entornos como WordPress y Gatsby
Qué voy a aprender en este curso
Lo básico y no tan básico de JavaScript, el lenguaje que parece que lo domina todo (en desarrollo web) con la lucha centrada ya no en el lenguaje en sí sino en los distintos frameworks que compiten en popularidad
Utilizar sólo JavaScript (lo que se conoce con la expresión vanilla JavaScript o pure JavaScript) es válido para todo, pero tarde o temprano utilizarás distintos frameworks (sean frameworks reales o sólo librerías) que nos facilitan muchísimo la vida
Qué es JavaScript
JavaScript es un lenguaje sencillo, donde utilizo sencillo como sinónimo de no estructurado
La diferencia práctica entre un lenguaje estructurado y uno no estructurado es que en el primero escribes y ejecutas, mientras que el segundo escribes, compilas, y ejecutas
Parece una diferencia insignificante, pero tiene un impacto mayúsculo en la productividad, esto es, JavaScript y cualquier lenguaje no estructurado normalmente permiten un progreso muy rápido a costa de perder potencia y complicar su escalabilidad (cuando los proyectos crecen)
Para qué sirve
-
Para programación web, ejecutado por el navegador, esto es frontend
-
Para programación en servidores, ejecutado por servidores, esto es backend (con NodeJS)
-
Para programación en aplicaciones móviles (Ionic) o de escritorio (ElectronJS)
Es buena idea aprender JavaScript
JavaScript representa uno de los lenguajes más populares del momento, y esa tendencia no sólo es estable sino que va en aumento
La ley de Atwood (cofundador de Stack Overflow) decía lo siguiente en 2017: "Cualquier aplicación que pueda escribirse en JavaScript se terminará escribiendo en JavaScript", y de momento parece que está muy vigente
La razón es porque con JavaScript puedes olvidarte de todo lo que es más farragoso y centrarte exclusivamente (más o menos) en la lógica del programa, y si bien hace 20 años esto era un suicidio en términos de potencia, hoy es más que factible para la gran mayoría de aplicaciones que no requieren de grandes eficiencias
Y ahora sí, vamos allá
Los elementos básicos
Lo primero es prepararte el entorno de trabajo, lo he separado aparte y lo puedes encontrar aquí
Variables
Las variables pueden ser valores primitivos o pueden no serlo
Cuando asignamos un valor primitivo almacenamos literalmente ese valor
Cuando no es un valor primitivo lo que almacenamos es una referencia a ese valor
El símil con un edificio sería que el valor del edificio es literalmente el edificio, mientras que la referencia al edificio es un papel donde está escrita la dirección donde podemos encontrar ese edificio
String
Un string
es un valor primitivo, y lo podemos asignar de 3 maneras distintas
var content = 'hola'
const content = 'hola'
let content = 'hola'
La primera, var
, es la antigua, descártala (aunque es válida en el sentido de que no da error)
La segunda, const
, sirve para definir esas variables que no modificaremos, úsala siempre porque
- Te protege por si por error luego intentas modificarla y no querías (te dará error)
- Te facilita la lectura del código (al saber que es una constante sabes que su valor no cambiará)
- Te da cierta mejora en eficiencia (seguramente negligible, pero algo es algo)
La tercera, let
, es el equivalente moderno de 'var' (aunque el scope es más pequeño, pero esto no aplica aquí), úsala cuando necesites que tu variable sea mutable
El error típico es utilizar por defecto let
, no lo hagas, utiliza siempre const
(excepto cuando necesites let
)
Acerca de las comillas
- las dobles
"
y las simples'
son equivalentes - Las inclinadas
`
ejecutan el código
const queTal = 'bien'
const hola = '${queTal} o qué'
const hola2 = `${queTal} o qué`
console.log(hola) // ${queTal} o qué
console.log(hola2) // bien o qué tal
Es decir, en el segundo caso se ejecuta lo que hay dentro, y la manera de transformar variables es utilizar la construcción ${}
Y claro está que las comillas son necesarias, si no lo que haríamos sería hacer una copia de una variable (o un error si esa variable no existe)
const hola = 'que tal'
const content = hola
const content2 = 'hola'
console.log(content) // que tal
console.log(content2) // hola
const content3 = hola2 // error, la variable hola2 no existe
Para terminar, qué gran ventaja nos aportan las comillas inclinadas? Poder hacer multi-linea
const bye = 'hasta luego'
const content1 = 'hola \n' + 'qué tal \n' + 'como vamos \n' + bye
console.log(content1)
// hola
// qué tal
// como vamos
// hasta luego
const content2 = `hola
qué tal
como vamos
${bye}`
console.log(content3)
// hola
// qué tal
// como vamos
// hasta luego
No hay color
Ah, y acerca de los nombres de variables, no se permiten variables que empiecen por números o con guiones normales
Number
Los number se definen igual que las string pero sin comillas (si utilizas comillas tendrás un string, y los strings se concatenan)
Eso sí, podemos convertir strings en números con parseInt
(convierte '3as' en el número 3) o Number
(convierte '3' en 3, pero '3as' nos dice que no es un número)
const num1 = 1
const num2 = 2
const num3 = '3' // ya no es un número sino un string!
const num4 = '4rt' // ya no es un número sino un string!
console.log(num1 + num2 + num3 + num4) // 334rt
console.log(num1 + num2 + parseInt(num3) + parseInt(num4)) // 10
console.log(num1 + num2 + Number(num3) + Number(num4)) // NaN -> Error Not A Number
Array y Object
La definición para ambos sería de un conjunto de elementos, y la diferencia está entre cómo organizan ese conjunto de elementos
-
Un
Array
es iterable -
Un
Object
no lo es
const arr = new Array() // creamos un array
const arr = [] // lo mismo
const obj = new Object() // creamos un objeto
const obj = {} // lo mismo
Así se definen los arrays y los objects (2 maneras distintas para cada uno, escoge la corta)
Un array se podría entender como un objeto con las key
definidas e inalterables, algo así
const arr = ['hola', 'que tal']
const pseudoArr = { 0: 'hola', 1: 'que tal' } // da error puesto que no se pueden definir nombres de variables que empiecen con un número
const obj = { hola: 'hola', queTal: 'que tal' }
Cuándo utilizaremos un array
y cuando un object
?
Un object
tiene sentido cuando queremos definir por ejemplo un post
const post1 = { title: 'post número 1', status: 'publicado' }
const post2 = { title: 'post número 2', status: 'publicado' }
Y cuándo un array
? Por ejemplo cuando queramos almacenar todos estos posts
const posts = [post1, post2]
O podríamos hacerlo directamente, un array
de objects
const posts = [
{ title: 'post número 1', status: 'publicado' },
{ title: 'post número 2', status: 'publicado' },
]
Y para acceder a las propiedades de cada estructura? Tenemos una notación para el array, y dos notaciones para el object
const posts = [
{ title: 'post número 1', status: 'publicado' },
{ title: 'post número 2', status: 'publicado' },
]
console.log(posts[0]) // { title: 'post número 1', status: 'publicado' }
console.log(posts[0].title) // post número 1
console.log(posts[0]['title']) // post número 1
Con un object
siempre utilizaremos la notación con el punto, excepto cuando no sepamos la propiedad
Por ejemplo
const responder = status => {
const usuarios = { visitante: 'me gusta', cliente: 'me gusta aún más' }
let type = 'visitante'
if (status === 'logeado') type = 'cliente'
return usuarios[type]
}
const mensaje = responder('no logeado')
console.log(mensaje) // me gusta
Si hubiésemos escrito return usuarios.type
nos daría error porque ese objeto no tiene ninguna propiedad que se llame type
(y si la tuviera, aquí tendríamos uno de esos bugs terribles de solucionar ya que no daría error)
No son las únicas estructuras en JavaScript, pero son las más utilizadas con diferencia, y las otras son una especie de "mezclas" entre estas dos
Otras diferencias entre arrays
y objects
? Las funciones o métodos que tienen asociados
Esas funciones son propiedades de los objetos
- Si tenemos un array no podemos añadir ninguna nueva función
- Pero si tenemos un objeto sí que podemos
Por ejemplo
const post = { title: 'post number 1', status: 'published', console: text => console.log(text) }
console.log(post.title) // post number 1
post.console('hola') // hola
Este es un método inútil, pero me sirve para que veas que podemos poner una función sin problemas dentro de un objeto
(Luego hablaré de cómo definir una función y su nomenclatura)
Pues del mismo modo que llamamos a una función de nuestro objeto como una variable, hacemos lo mismo con las funciones que nos vienen de serie
Un ejemplo (más adelante veremos los más importantes)
const arr = [] // definimos un array
arr.push('hola qué tal') // con el método push() añadimos un elemento por el final
arr.push('bien')
console.log(arr[0]) // hola qué tal
console.log(arr[1]) // bien
// con el método map() iteramos dentro del array
arr.map(el => console.log(el)) // hola qué tal, bien
Comparaciones
Las comparaciones son un elemento que utilizaremos masivamente, y son muy simples, nos sirven para actuar de una forma o de otra en función de un condicional
const obj = { hola: 'hola que tal' }
if (obj.hola === 'hola que tal') {
console.log('bien')
} else {
console.log('mal')
}
// bien
Ya que la comparación es cierta se mostrará en la consola bien
Podemos quitar los paréntesis si sólo tenemos una orden a ejecutar, para mi queda todo más conciso
const obj = { hola: 'hola que tal', queTal: 'regular' }
if (obj.hola === 'hola que tal') console.log('bien')
else console.log('mal')
// bien
Tenemos otra manera de escribir los if
y else
, que puede parecer innecesaria
const obj = { hola: 'hola que tal', queTal: 'regular' }
obj.hola === 'hola que tal' ? console.log('bien') : console.log('mal') // bien
Queda aún más compacto, pero la diferencia no es esa
La diferencia es que esta nomenclatura nos permite reescribir lo anterior de esta manera
const obj = { hola: 'hola que tal', queTal: 'regular' }
console.log(obj.hola === 'hola que tal' ? 'bien' : 'mal') // bien
Ahora no es que quede más compacto, es que queda declarativo en lugar de imperativo
-
Imperativo: primero defines el cómo (voy a hacer un if) y luego el qué (para hacer un console.log)
-
Declarativo: primero defines el qué (voy a hacer un console.log) y luego el cómo (mediante un if)
Esta "nimiedad" en realidad te permite leer y estructurar tu cabeza mucho mejor ya que en general, el código lo hacer para hacer cosas, el cómo las haces es secundario
Y volviendo a la comparación, fíjate que ésta es con tres signos de igual ===
, esto es así porque tres signos equivale a una equivalencia estricta
Si ponemos dos ==
quiere decir que no tiene porqué ser estricta
Y si ponemos un =
no hacemos comparación, hacemos asignación (que siempre será cierta)
Lo recomendable es siempre utilizar los tres signos, excepto en algunos casos concretos (como detectar valores no definidos)
Lo ves en el siguiente código, y ojo a la última línea, estos bugs son terribles de detectar
const var1 = 1 // es un Number
const var2 = '1' // es un String
console.log(var1 == var2) // true
console.log(var1 === var2) // false
console.log(var1 === Number(var2)) // true
console.log((var1 = var2)) // ERROR, porque var1 es una constante y no se puede volver a asignar, esto nos ha salvado!
Operadores lógicos
Otra manera de escribir condicionales son con los operadores lógicos, los AND y OR que en JavaScript se escriben &&
y ||
Son interesantes porque también nos van muy bien en programación declarativa o funcional
const obj = { usuario: 'yomismo', status: 'valid' }
console.log((obj.usuario === 'yomismo' && obj.status === 'valid' && 'bien') || 'mal') // bien
Loops
Seguimos con los loops, funciones para iterar sobre estructuras como arrays o objects
Las estructuras se separan entre iterables y no iterables
-
Los arrays son iterables
-
Los objects no lo son
Los loops que no son útiles para programación funcional siempre necesitan iterables, por lo que tendremos que convertir nuestros objects
en iterables
Y también tenemos loops del tipo imperativo que nos permiten iterar sobre no iterables, pero ojo, porque el orden de los elementos no está garantizado (en la práctica sí lo está)
(imperativo) for..in y for..of
[ me ahorro los paréntesis siempre que sea posible ]
const arr = ['uno', 'dos', 'tres']
for (let num in arr) console.log(num + ' - ' + arr[num])
// 0 - uno
// 1 - dos
// 2 - tres
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
for (let prop in obj) console.log(prop + ' - ' + obj[prop])
// hola - uno
// queTal - dos
// comoVamos - tres
Con el for in
puedo iterar objetos y arrays
Técnicamente, un for in
con objetos tendría que asegurarse que no tenemos propiedades "extra" en los objetos
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
for (let prop in obj) obj.hasOwnProperty(prop) && console.log(prop + ' - ' + obj[prop])
// hola - uno
// queTal - dos
// comoVamos - tres
Cuándo tendremos ese caso de tener alguna propiedad no esperada en un objeto? Cuando la hayamos heredado de algún otro objeto, así que si no trabajamos con clases y objetos (algo que no necesitarás si adoptas una programación funcional) no será necesario
const arr = ['uno', 'dos', 'tres']
for (let num of arr) console.log(num)
// uno
// dos
// tres
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
for (let prop of obj) console.log(prop)
// ERROR, obj is not iterable
Con el for of
sólo puedo iterar iterables, es decir, arrays
Para hacerlo con objetos deberíamos convertir el object
en un array con Object.keys()
, Object.values()
o Object.entries()
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
for (const prop of Object.keys(obj)) console.log(prop + ' - ' + obj[prop])
// hola - uno
// queTal - dos
// comoVamos - tres
for (const prop of Object.values(obj)) console.log(prop)
// uno
// dos
// tres
for (const prop of Object.entries(obj)) console.log(prop[0] + ' - ' + prop[1])
// hola - uno
// queTal - dos
// comoVamos - tres
- Con
Object.keys()
lo que hacemos es sacar unarray
de laskeys
del objeto - Con
Object.values()
lo que hacemos es sacar unarray
de lasvalues
del objeto - Con
Object.entries()
lo que hacemos es sacar unarray
dearrays
con las parekaskey - value
del objeto
Por qué puedo utilizar const prop
? No debería utilizar let
?
Aquí puedo utilizar const
porque estos loops, a cada nueva iteración crean una nueva variable (mismo nombre, scope distinto), no reutilizan la misma
Desestructuring
Aprovecho aquí para introducir la desestructuración de las variables
Si tenemos un objeto, sabemo cómo acceder a sus propiedades (lo hemos visto antes)
Bien, pues hay otra manera de hacerlo
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
// Asigno 3 variables para que me sea más cómodo acceder a las propiedades del objeto
const hola = obj.hola
const queTal = obj.queTal
const comoVamos = obj.comoVamos
// O puedo hacer lo mismo con desestructuringç
const { hola } = obj
const { queTal } = obj
const { comoVamos } = obj
// O puedo hacerlo todo en la misma frase
const { hola, queTal, comoVamos } = obj
// y con los arrays también
const arr = ['uno', 'dos', 'tres']
const [hola, queTal, comoVamos] = arr
Si esto lo aplico a los loops, puedo hacer lo siguiente
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
// forma sin desestructurar
for (const entry of Object.entries(obj)) console.log(entry[0] + ' - ' + entry[1])
// hola - uno
// queTal - dos
// comoVamos - tres
// y aquí desestructuro entry
for (const [key, value] of Object.entries(obj)) console.log(key + ' - ' + value)
// hola - uno
// queTal - dos
// comoVamos - tres
(declarativo) forEach y map
Ésta es la versión declarativa de los loops, y sólo funciona con iterables
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
Object.entries(obj).forEach(function (el) {
console.log(el)
})
// ["hola", "uno"]
// ["queTal", "dos"]
// ["comoVamos", "tres"]
Aquí hemos definido una función, esto lo hacemos con
function miNombre(props) {
console.log(props)
}
O en la nomenclatura más moderna y funcional
const miNombre = props => {
console.log(props)
}
Luego hablo más en detalle
Con cada iteración tenemos una función que recibe el argumento que decidimos llamarle el
(de elemento), que en JavaScript más moderno la reescribiría así
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
Object.entries(obj).forEach(el => console.log(el))
// ["hola", "uno"]
// ["queTal", "dos"]
// ["comoVamos", "tres"]
Una de las diferencias entre este loop y los anteriores es que aquí podemos devolver valores
const whatever = for (const p in obj) {} //ERROR ESTO NO SE PUEDE HACER!
Esto de arriba no se puede hacer, pero con forEach
sí
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
const newObj = Object.entries(obj).forEach(([prop, val]) => {
return { prop: val + ' y eso' }
})
console.log(obj) // {hola: "uno", queTal: "dos", comoVamos: "tres"}
console.log(newObj) // undefined
Un momento, esto no funciona (aunque no da error)
Por qué?
Porque forEach
no devuelve valores
Pero map
sí
const obj = { hola: 'uno', queTal: 'dos', comoVamos: 'tres' }
const newObj = Object.entries(obj).map(([prop, val]) => {
return { prop: val + ' y eso' }
})
console.log(obj) // {hola: "uno", queTal: "dos", comoVamos: "tres"}
console.log(newObj) // [{prop: "uno y eso"}, {prop: "dos y eso"}, {prop: "tres y eso"}]
Perfecto, ahora ya sí funciona, podemos integrar un loop con una variable, ya se intuye la de cosas que podemos hacer así
Pero ... en realidad esto no hace lo que esperábamos
Queríamos un objeto así {hola: "uno y eso", queTal: "dos y eso", comoVamos: "tres y eso"}
Y tenemos esto otro [{prop: "uno y eso"}, {prop: "dos y eso"}, {prop: "tres y eso"}]
Un array de objetos y con las propiedades prop
en lugar de hola
, queTal
, etc
Qué está fallando
- Primero, que estamos definiendo la propiedad
prop
y no la variableprop
Para solucionarlo tenemos que poner prop
entre corchetes [prop]
return { [prop]: val + ' y eso' }
- Segundo, que map itera sobre un iterable y por lo tanto devuelve un iterable, esto es un elemento de array
El problema de base es que estamos transformando el objeto con Object.entries()
, y deberíamos revertirlo
Dicho esto y de cualquier modo ya hemos visto cómo funciona map()
, y créeme que es relevante porque es posiblemente el loop más utilizado en React
Ejemplo rápido para ver un uso habitual de map
con React
const users = [
{ name: 'holocausto', score: 200, status: 'activo' },
{ name: 'terremoto', score: 150, status: 'activo' },
{ name: 'riachuelo', score: 2000, status: 'esperando' },
]
const Users = () => (
<div>{users.map(el => `<span>usuario ${el.name}, puntos ${el.score}, estado ${el.status}</span>`)}</div>
)
Aquí el componente <Users />
nos devolverá una estructura html
de datos que incorpora los datos de users
Funciones
Las funciones nos sirven para encapsular y aislar partes de código para facilitar así la lectura, y si aplica poder re-utilizar ese código en concreto
Se las puede encapsular en objetos (les podemos llamar métodos) o en módulos o en complementos, pero siguen siendo funciones
La sintáxis es simple
function saludo(formal) {
console.log(formal ? 'buenos días' : 'pppeeeeehhhh')
}
saludo() // pppeeeeehhhh
saludo(1) // buenos días
Podemos encapsular la función en una variable, el resultado es el mismo pero la lectura mejora
const saludo = function (formal) {
console.log(formal ? 'buenos días' : 'pppeeeeehhhh')
}
saludo() // pppeeeeehhhh
saludo(1) // buenos días
Y podemos utilizar la nomenclatura fat arrows, el resultado sigue siendo el mismo pero la lectura mejora aún más
const saludo = formal => {
console.log(formal ? 'buenos días' : 'pppeeeeehhhh')
}
saludo() // pppeeeeehhhh
saludo(1) // buenos días
- Si tenemos más de un argumento los paréntesis
(formal, otroArgumento)
son obligatorios
Yo los quito siempre que puedo
- Y si no especificamos un valor a retornar, se retorna la última expresión ejecutada, y podemos aprovechar esto para eliminar paréntesis si sólo tenemos una instrucción
const saludo = formal => console.log(formal ? 'buenos días' : 'pppeeeeehhhh')
saludo() // pppeeeeehhhh
saludo(1) // buenos días
Mucho más conciso
Si recuerdas el ejemplo anterior de un componente de React
con map()
, reconocerás que ese código se parecía mucho al código de arriba
Son lo mismo
Los componentes de React
son funciones
Pero eso sí, para que sean un componente tienen que empezar con mayúscula, y tienen que devolver html
En este caso, podríamos escribir un componente de esta forma
const Saludo = props => <div>{props.formal ? 'buenos días' : 'pppeeeeehhhh'}</div>
<Saludo formal=0 />
<Saludo formal=1 />
// <div>pppeeeeehhhh</div>
// <div>buenos días</div>
Objetos
En JavaScript (casi) todo es un objeto
Pero eso no quiere decir que en JavaScript se tenga que programar con el paradigma de orientación a objetos (OOP)
En mi caso siempre huyo de la programación orientada a objetos, puesto que dificulta la lectura y te obliga a programar de forma muy redundante (en el sentido de tener que escribir mucho código para hacer poca cosa)
Pero hay que conocer cómo funciona porque existe muchísimo código escrito en OOP
Por ejemplo, en OOP hay que encapsular los objetos en clases (todo ello buscando una mejor separación de responsabilidades entre las distintas partes del código)
// definimos la clase User
class User {
// en el constructor se asignan dos variables por defecto
constructor(name, people) {
this.name = name
this.people = people
}
// definimos un método
echoName() {
console.log(this.name)
}
// definimos otro método
echoPeople() {
const that = this // esto es sucio, pero tenemos que hacerlo porque this se pierde dentro del forEach
this.people.forEach(function (el) {
console.log(el + ' en relación a ' + that.name)
})
}
// definimos otro método
echoPeople2() {
this.people.forEach(function (el) {
console.log(el + ' en relación a ' + this.name)
}, this) // aquí hacemos un bind the this para evitar el problema de echoPeople
}
// definimos otro método
echoPeople3() {
this.people.forEach(el => {
// la notación fat arrow nos hace el bind automáticamente
console.log(el + ' en relación a ' + this.name)
})
}
}
let obj = new User('ingastain', ['ingastain abuelo', 'ingastain abuela'])
obj.echoName() // ingastain
obj.echoPeople() // ingastain abuelo en relación a ingastain, ingastain abuela en relación a ingastain
obj.echoPeople2() // ingastain abuelo en relación a ingastain, ingastain abuela en relación a ingastain
obj.echoPeople3() // ingastain abuelo en relación a ingastain, ingastain abuela en relación a ingastain
No me entretengo con el código, para ya se puede intuir que la variable this
no despierta simpatías
En programación funcional dejamos de tener ese problema, dicho queda
Sobre las referencias
En Javascript hay referencias y valores
Referencias, punteros, todo esto suena a complejidad y lenguaje estructurado, JavaScript prometió liberarnos de estos conceptos que al final sirven para que podamos aumentar la eficiencia a costa de reducir la productividad
Y sí, es verdad, en JavaScript no vemos ni las referencias ni los valores por ningún lado, simplemente están
Pero aunque no las vemos no las podemos ignorar porque nos traen ciertos problemas de los que tenemos que ser conscientes
Diferencias entre valores y referencias
Con el símil de la casa, una referencia de la variable casa sería equivalente a su dirección, mientras que el valor de la variable casa sería la propia casa
Puesto que sale más barato pasarle a un amigo una dirección y no una casa entera, en JavaScript todo se pasa por referencia, excepto los valores primitivos
Técnicamente todo se pasa por valor, pero esos valores todos guardan una referencia excepto para los primitivos, donde se guarda el propio valor
const obj = { name: 'diseldorf' } // object: no es primitivo
const newObj = obj // object: no es primitivo
newObj.name = 'gasoldorf' // propiedad de object que es un string: el string sí es primitivo, pero el object no
console.log(obj.name) // gasoldorf
console.log(newObj.name) // gasoldorf
Qué ha pasado aquí? Que obj
y newObj
apuntan los dos a la referencia del mismo objeto
Qué queríamos aquí? Clonar el objeto, o lo que es lo mismo, hacer una copia por valor y no por referencia
Si lo que queremos es clonar objetos o arrays, lo explico en esta y esta entrada del blog
La más directa y universal, hacer un JSON.stringify()
y JSON.parse()
const obj = {
name: 'diseldorf',
children: ['disel', 'gasol', 'sun'],
objectives: {
shortTerm: 'learn',
midTerm: 'apply',
longTerm: 'fulfillment',
},
who: () => 'I am diseldorf!',
}
let newObj2 = JSON.parse(JSON.stringify(obj))
newObj2.name = 'gasoldorf'
newObj2.children = ['gasol junior']
newObj2.objectives.shortTerm = 'naah'
newObj2.objectives.midTerm = 'naah'
newObj2.objectives.longTerm = 'naah'
console.log(obj.name)
console.log(obj.children)
console.log(obj.objectives)
console.log(obj.who())
console.log(newObj2.name)
console.log(newObj2.children)
console.log(newObj2.objectives)
console.log(newObj2.who())
// diseldorf
// ["disel", "gasol", "sun"]
// {shortTerm: "learn", midTerm: "apply", longTerm: "fulfillment"}
// I am diseldorf!
//
// gasoldorf
// ["gasol junior"]
// {shortTerm: "naah", midTerm: "naah", longTerm: "naah"}
// ERROR: newObj2.who is not a function
Con esta manera lo copiamos todo excepto los métodos ya que estos se pierden
Módulos
Si en programación funcional ya no utilizamos las clases, aquí están los módulos para facilitarnos la segmentación de código y que podamos escalar sin que nos explote el cerebro (compartimenta y vencerás)
Export e Import
Para utilizar esos módulos tenemos por un lado que importarlos y por el otro haberlos exportado
Por ejemplo
// modulo.js
export const user = name => ({ name: name })
// main.js
import { user } from './modulo.js' // estoy desestructurando
const myUser = user('diseldorf')
console.log(myUser) // { name: 'diseldorf' }
// main.js
import { user as createUser } from './modulo.js' // sigo desestructurando y le cambio el nombre
const myUser = createUser('diseldorf')
console.log(myUser) // { name: 'diseldorf' }
// main.js
import * as mod from './modulo.js' // aquí lo importo todo
const myUser = mod.user('diseldorf')
console.log(myUser) // { name: 'diseldorf' }
Dos maneras de importar
-
O bien destructurando e importando sólo aquellas funciones que queremos
-
O bien importando el módulo entero en forma de objeto que nos sirve para luego acceder a sus propiedades
Si importamos sólo lo que queremos ganaremos en eficiencia
Fíjate que el as
nos sirve para cambiar de nombre lo que importamos (un alias), también podemos hacerlo en el export
// modulo.js
const user = name => ({ name: name })
export { user as getUser }
Y ojo porque podemos exportar funciones, variables o clases, todo es exportable e importable
Finalmente, si sólo tenemos una función a exportar (o queremos exportar una función por defecto, y las otras que haya que importarlas explícitamente) podemos utilizar lo siguiente
// modulo.js
const user = name => ({ name: name })
export { user as default }
...
// main.js
import user from './modulo.js'
const myUser = user('diseldorf')
console.log(myUser) // { name: 'diseldorf' }
e incluso podemos ahorrarnos el nombre
// modulo.js
export default name => ({ name: name })
...
// main.js
import user from './modulo.js'
const myUser = user('diseldorf')
console.log(myUser) // { name: 'diseldorf' }
Eso sí, exportar como default
permite importar esa función con cualquier nombre, y eso no parece buena idea sobre todo si vamos a trabajar con más gente
Conclusión: parece más sensato exportar e importar siempre explicitando lo que queremos exportar e importar
CommonJS
Hasta aquí todo parece tan sencillo ...
Pero tarde o temprano programaremos para NodeJS
, por ejemplo en la parte del backend
de GatsbyJS
Bien
Todo lo que hemos visto aquí se refiere a JavaScript moderno ES6
Pero NodeJS está aún (y lo que le queda) migrando de CommonJS
a ES6
En la práctica?
-
En lugar de
import
necesitaremos utilizar unrequire
-
Y otras menudeces que molestan más de lo que puede parecer, pero de las que no toca hablar aquí
Debug o encontrar el error
Del tiempo total, pasarás el doble corrigiendo errores, y solucionar esos errores te costarán el doble de tiempo de lo que te costó programar la primera versión
Esto no sé quién lo dijo y no tiene porqué ser cierto, dependerá de muchos factores, pero lo que sí es cierto es que siempre habrán errores a corregir, eso sí que es indiscutible
Solucionarlos acostumbra a ser sencillo (con honrosas excepciones), lo complicado suele ser identificarlos, porque no siempre tendremos el mensaje de error indicándonos dónde se encuentra este
La estrategia siempre es la misma, ir acotando código hasta que podamos aislar esa línea que nos está dando el error
Y para acotar, en JavaScript tenemos el omnipresente console.log()
que nos envía mensajes a la consola (por ejemplo del navegador)
El objetivo?
- Localizar el error
Console.log
En Chrome
(aunque esto funciona para todos los navegadores) tienes la consola abriendo la pestaña de Developers Tools
donde accederemos a varias herramientas como la del explorador de los elementos html
y css
y la consola de JavaScript
El Developer Tools
nos sirve para un montón de cosas, desde estudiar la eficacia de la página hasta ver qué estamos almacenando en el navegador (cookies
y localStorage
)
Y es en la consola donde tendremos todos los mensajes de error que habrá que explorar y todos nuestros console.logs()
que hayamos puesto
Breakpoints
También podemos explorar el código directamente en Chrome
y allí pararlo y estudiarlo in-situ
El problema?
Que si trabajamos con un framework, éste se encargará de transformar nuestro JavaScript moderno a código más antiguo y compatible
Pues eso, que no podremos hacer breakpoints en el código original, lo que destroza la utilidad de esta herramienta
Debugger
Y relacionado con los breakpoints
, tenemos el debugger;
Si añadimos la expresión debugger;
en cualquier parte de nuestro código, si existe un debugger (como el de Chrome) este pondrá un breakpoint allí y podremos paralizar la ejecución y explorar todas las variables que queramos
Seguirá siendo código compilado, pero tendremos mucho más fácil localizar dónde estamos
Otros
-
También tenemos aparte del
console.log()
elconsole.table()
que nos dará un formato más legible para los objetos -
Si queremos evaluar cuánto tarda una parte del código (un loop por ejemplo) podemos monitorizarlo poniendo la parte del código entre
console.time('nombre')
yconsole.timeEnd('nombre')
, y en la consola nos aparecerá el tiempo transcurrido -
También podemos monitorear una función específica, escribiendo
monitor(función)
y cada vez que sea llamada nos dirá los valores que ha recibido
Comentario final
Y ya está, ya lo tenemos, ya hemos terminado el curso básico de JavaScript
El objetivo de este curso es el que hayas aprendido lo mínimo (aunque es mucho) de JavaScript para que puedas seguir progresando sin obstáculos
Aunque ya sabes que no se aprende de leer, se aprende de hacer
Stop Learning, Start Doing
🙋♂️