馃棑 Publicado el 6 de agosto de 2019
馃嚩 & 馃嚘's sobre temas y conceptos de JavaScript a manera de preparaci贸n para entrevistas laborales
En JavaScript cada declaraci贸n de funci贸n o variable es llevada hasta arriba de su scope actual, lo cual es llamado
hoisting.
var, let y const?La principal diferencia radica en el scope de cada una de ellas. El t茅rmino scope hace referencia a su alcance de uso (o su disponibilidad para usarse).
var tiene:
var fuera de una funci贸n est谩 disponible para usarse en toda el objeto window o el script donde reside.var pueden ser redeclaradas y su valor puede actualizarseundefinedlet tiene:
{ y } lo que implica que las variables declaradas con let 煤nicamente est谩n disponibles dentro del bloque de c贸digo en el que se declararon.let pueden actualizar su valor, pero no pueden ser redeclaradas (aunque dos variables declaradas con let pueden llamarse igual siempre y cuando no est茅n en el mismo bloque de c贸digo, ergo no ser谩n la misma variable).const tiene:
const deben ser inicializadas al momento de su declaraci贸n, y no pueden actualizar su valor, ni redeclararseconst, dado que el objeto como tal no podr谩 actualizar su valor, pero sus propiedades SI podr谩nundefined y not defined?Si se intenta usar una variable que no existe y no ha sido declarada, JavaScript arrojar谩 el error var name is not defined y el script detendr谩 su ejecuci贸n. Pero si se utiliza 麓typeof variable_no_declarada麓 茅ste retornar谩 undefined.
Antes de abundar en el tema, hay que entender la diferencia entre declaraci贸n y definici贸n.
Podemos decir que var x es una declaraci贸n porque todav铆a no se ha definido el valor que almacenar谩 x. Pero su existencia ya se declar贸, as铆 como la necesidad de asignar memoria.
var x; //declarando x
console.log(x) //undefined
Aqu铆 var x = 1 es ambas: declaraci贸n y definici贸n (tambi茅n se le puede llamar inicializaci贸n). En el ejemplo anterior, la declaraci贸n y asignaci贸n del valor suceden en la misma l铆nea.
La asignaci贸n sucede en orden, de manera que cuando intentamos acceder a una variable que ha sido declarada, pero no ha sido definida, obtenemos el resultado undefined.
var x; //declaraci贸n
console.log(typeof x === undefined) //true
Si una variable no ha sido ni declarada ni definida, cuando intentemos hacer referencia a dicha variable, obtendremos not defined.
console.log(y) //ReferenceError: y is not defined
var y = 1
if (function f(){}) {
y += typeof f
}
console.log(y)
El resultado ser谩 1undefined.
El condicional if es evaluado utilizando eval, por lo que eval (function f(){}) retorna el valor function f(){} el cual es true, por lo que dentro del bloque del if, typeof f retorna undefinedporque el bloque del if ejecuta su c贸digo en tiempo de ejecuci贸n, y la condici贸n del if es evaluada tambi茅n durante tiempo de ejecuci贸n.
var k = 1
if (1) {
eval(function foo(){})
k += typeof foo
}
console.log(k)
El c贸digo anterior tambi茅n imprimir谩 1undefined.
var k = 1
if (1) {
function foo(){}
k += typeof foo
}
console.log(k)
El c贸digo anterior imprimir谩 1function.
Una de las desventajas de crear m茅todos privados en JavaScript es que son ineficientes en cuanto al uso de memoria, puesto que cada nueva instancia de la clase tendr谩 su copia 煤nica de dicho m茅todo.
var Empleado = function (nombre, compania, salario) {
this.nombre = nombre || ''
this.compania = compania || ''
this.salario = salario || 5000
// Metodo privado
var incrementarSalario = function () {
this.salario += 1000
}
// Metodo publico
this.mostrarSalarioIncrementado = function () {
incrementarSalario()
console.log(this.salario)
}
}
var emp1 = new Empleado('John', 'HBO', 3000)
var emp2 = new Empleado('Daenerys', 'HBO', 2000)
var emp3 = new Empleado('Bran', 'HBO', 5500)
Cada variable de instancia emp1, emp2 y emp3 tendr谩 su propia copia del m茅todo privado incrementarSalario.
Una closure (o cerradura) es una funci贸n definida dentro de otra funci贸n (llamada funci贸n padre), y tiene acceso a las variables que han sido declaradas y definidas en el scope de su funci贸n padre.
La closure tiene acceso a las variables en tres diferentes scopes:
let globalVar = 'abc';
// Funcion padre auto invocada
(function outerFunction (outerArg) { //Inicio del scope padre
let outerFuncVar = 'x'; //Variable declarada dentro del scope de outerFunc
// Funcion closure auto invocada
(function innerFunction (innerArg) {
let innerFuncVar = 'z';
console.log(`
outerArg = ${outerArg}
outerFuncVar = ${outerFuncVar}
innerArg = ${innerArg}
innerFuncVar = ${innerFuncVar}
globalVar = ${globalVar}`)
})('y')
})('w')
innerFunction es una closure definida dentro de outerFunction por lo que tiene acceso a todas las variables declaradas y definidas dentro del scope de outerFunction, as铆 como a todas las variables definidas en el espacio de nombres global.
La salida del c贸digo anterior ser谩:
outerArg = w
outerFuncVar = x
innerArg = y
innerFuncVar = z
globalVar = abc
multip que produzca la siguiente salida al invocarse:console.log(multip(2)(3)(4)) // 24
console.log(multip(4)(5)(6)) // 120
La soluci贸n es la siguiente:
function multip (a) {
return function (b) {
return function (c) {
return a * b * c
}
}
}
La funci贸n multip retorna una funci贸n an贸nima, que a su vez retorna otra funci贸n an贸nima que retorna la multiplicaci贸n de los argumentos de cada funci贸n.
Como se mencion贸 anteriormente, una funci贸n definida dentro de otra tendr谩 acceso a las variables de la funci贸n padre, y
ObjectTeniendo el arreglo
let arr = ['a', 'b', 'c', 'd', 'e']
Opci贸n 1:
arr = []
Este m茅todo actualiza el valor de la variable arr a un arreglo vac铆o y se recomienda s贸lo si no existen referencias previas a la variable arr dado que si existen referencias previas, 茅stas no se actualizar谩n. Por ejemplo:
let arr = ['a', 'b', 'c', 'd', 'e']
let otroArr = arr
arr = []
console.log(otroArr) // ['a', 'b', 'c', 'd', 'e']
Opci贸n 2:
arr.length = 0
Esta opci贸n actualiza el arreglo estableciendo su longitud a 0. Se recomienda este m茅todo si existen referencias a la variable arr y se desean actualizar todas las referencias. Por ejemplo:
let arr = ['a', 'b', 'c', 'd', 'e']
let otroArr = arr
arr.length = 0
console.log(otroArr) // []
Opci贸n 3:
arr.splice(0, arr.length)
Esta forma de vaciar el arreglo tambi茅n actualizar谩 todas las referencias al mismo
Opci贸n 4:
while (arr.length) {
arr.pop()
}
Este m茅todo no se recomienda.
Podemos verificar si un valor determinado es un arreglo usando el m茅todo Array.isArray() disponible en ES6 o superior
let arr = ['a', 'b', 'c', 'd', 'e']`
La mejor forma de verificar si un objeto es instancia de una clase particular es usando el m茅todo Object.prototype.toString. Tomando como ejemplo
let salida = (function (x) {
delete x
return x
})(0)
console.log(salida)
El resultado ser谩 0 dado que la instrucci贸n delete funciona 煤nicamente para propiedades de objetos y en este caso x es una variable local.
let x = 1
let salida = (function () {
delete x
return x
})()
console.log(salida)
El resultado ser谩 1 porque x no es una propiedad de un objeto, sino una variable global.
let x = {foo: 'bar'}
let salida = (function () {
delete x.foo
return x.foo
})()
console.log(salida)
El resultado ser谩 undefined. En este caso el operador delete eliminar谩 la propiedad foo del objeto x. Posteriormente se intentar谩 referenciar a la propiedad eliminada, por ende el resultado undefined.
let Empleado = {
empresa: 'HBO'
}
let emp1 = Object.create(Empleado)
delete emp1.empresa
console.log(emp1.empresa)
El resultado ser谩 HBO. Aqu铆 emp1 tiene a empresa como propiedad de su prototype. El operador delete no elimina propiedades de prototype.
El objeto emp1 no tiene a empresa como propiedad propia (lo podemos verificar con un console.log(emp1.hasOwnProperty('empresa')) // false).
Aunque si podr铆amos eliminar la propiedad empresa directamente del objeto Empleado utilizando delete Employee.company o tambi茅n del objeto emp1 utilizando delete emp1.__proto__.empresa
let arr = [1, 2, 3, 4, 5, 6, 7]
delete arr[2]
console.log(arr.length)
El resultado ser谩 7 dado que el operador delete elimina el elemento del arreglo, pero no modifica su longitud. Esto es cierto incluso cuando se eliminan todos los elementos del mismo.
En algunos ambientes como Google Chrome o node, si intentamos imprimir en consola el arreglo, nos mostrar谩 algo como [ 1, 2, <1 empty item>, 4, 5, 6, 7 ]
let bar = true
console.log(bar + 0)
console.log(bar + 'xyz')
console.log(bar + true)
console.log(bar + false)
El resultado ser谩 1, "truexyz", 2, 1 esto por c贸mo se comporta el operador de adici贸n. Como gu铆a general tenemos:
let z = 1, y = z = typeof y
console.log(y)
El resultado ser谩 undefined. Esto es por la regla de asociatividad en la precedencia de operadores (en particular para el operador asignaci贸n, el cual tiene asociatividad de derecha a izquierda).
Lo que sucede en el c贸digo anterior es que primeramente se eval煤a el typeof y, el cual en ese momento es undefined. Posteriormente se asigna a z el valor undefined, y luego dicho valor a y. Finalmente se declara e inicializa z = 1.
// NFE o Named Function Expression
var oks = function bye () { return 12; };
typeof bye();
El resultado es un Reference Error. Para que el c贸digo funcione lo podemos reescribir de varias formas:
var oks = function () { return 12; };
typeof bye();
o como
function oks () { return 12; };
typeof bye();
Lo cual nos retornar谩 el valor de number.
La definici贸n de una funci贸n s贸lo puede tener una variable de referencia como nombre. En el segundo bloque de c贸digo la variable oks hace referencia a una funci贸n an贸nima, y en el tercer bloque, la definici贸n de la funci贸n es su propio nombre.
let oks = function () {
// codigo
}
function bye () {
// codigo
}
La principal diferencia es que la funci贸n oks es definida en tiempo de ejecuci贸n mientras que la funci贸n bye es definida en tiempo de an谩lisis. Para entender mejor veamos el siguiente ejemplo:
<script>
oks(); //Llamar a la funci贸n oks arrojar谩 un error
let oks = function () {
console.log('Hola mundo')
}
</script>
<script>
bye(); //Llamar a la funci贸n bye no arrojar谩 un error
function bye () {
console.log('Hola mundo')
}
</script>
Una ventaja de la primer forma de declarar funciones es que se pueden declarar con base en cierta condici贸n, no as铆 de la segunda forma.
var salario = '$10000';
(function () {
console.log(`El salario original es de ${salario}`)
var salario = '$15000'
console.log(`El nuevo salario es de ${salario}`)
})()
El resultado para salario ser铆a primero undefined y luego $15000 esto debido al hoisting para variables declaradas con var (explicado al inicio de esta publicaci贸n).
Suponiendo el siguiente arreglo asociativo:
let assoc = {
A: 1,
B: 2
}
assoc['C'] = 3
Podemos calcular su longitud utilizando el m茅todo Object.keys() (disponible a partir de ES6) de la siguiente manera:
console.log(Object.keys(assoc).length)
null, undefined y undeclared y c贸mo revisar dichos valores?
undefinedes una variable que ha sido declarada pero no tiene asignado un valor, tambi茅n puede ser un tipo de variabletypeof variable === undefinednullpuede ser el valor asignado a una variable para representar ausencia de un valor, tambi茅n es un tipo de objetotypeof null // objectundeclaredindica que una variable no ha sido declarada, al hacer referencia a ella se arrojar谩 unReferenceError: variable is not defined
== y ===?La diferencia es que el operador
==no compara el tipo de dato de los elementos a comparar y el===si. Por ejemplo:
0 == false // true, auto type coercion
0 === false // false, diferente tipo
1 == "1" // true, auto type coercion
1 === "1" // false, diferente tipo
Para iterar sobre las propiedades de un objeto podemos utilizar el bucle
for...iny para iterar sobre los elementos de un arreglo podemos utilizar el buclefor...of
Array.forEach() y el m茅todo Array.map()?El bucle
forEach()ejecuta una funci贸n una vez por cada elemento de un arreglo (mut谩ndolo), pero no entrega un valor de retorno.El m茅todo
map()crea un nuevo arreglo con el resultado de llamar una funci贸n en cada elemento del arreglo a mapear y retorna el nuevo arreglo (del mismo tama帽o que el original).
En operaciones as铆ncronas, las
callbacksuelen ser funciones an贸nimas
Un native object es un objeto definido por alguna especificaci贸n de ECMAScript (Object, Date, Math, parseInt, eval, String.indexOf, String.replace, etc).
Un objeto host es cualquier objeto que no sea un objeto nativo, como WebAPIs (XMLHttpRequest, setTimeout, getElementsByTagName, window, document, location, history, etc.).
Function.apply y Function.call?Ambas permiten especificar un valor de
thispara la funci贸n a invocar, pero la diferencia es queapplyte permite invocar a la funci贸n con sus argumentos especificados como unarreglo ycallrequiere que los argumentos sean listados espec铆ficamente (separados porcomas).
function Person(), var person = Person() y var person = new Person()?
function Person()es una declaraci贸n de una funci贸n.
var person = Person()retorna el valor de la funci贸nPerson()y se lo asigna a la variableperson
var person = new Person()crea una instancia basada en la funci贸nPerson()por lo quepersones un objeto.
function foo(){} y var foo = function(){}?La primera es una declaraci贸n de funci贸n, la cual es evaluada en la scope que la contiene y la segunda es una expresi贸n funci贸n, la cual crea una funci贸n an贸nima (aunque podr铆a tener un nombre) y la asigna a la variable
foo
Depende, pero por lo general convienen poner todos los scripts en un solo archivo minificado, puesto que se realizar谩 una sola petici贸n para obtener el archivo vs m煤ltiples. En el caso de JS en l铆nea (in-line) es recomendable ponerlo hasta abajo de la etiqueta body de cierre, de esta manera no bloquear谩 el render de la p谩gina.
Es una colecci贸n de datos que contienen propiedades (asociaci贸n entre pares llave-valor) y m茅todos. Cada elemento en un documento es un objeto manipulable gracias al DOM.
Ambos son m茅todos de propagaci贸n de eventos en la API del DOM HTML. El event bubbling ocasiona que todos los eventos en los nodos hijo sean pasados a los nodos padre. En el event capturing el elemento exterior captura el evento y lo propaga hacia los elementos internos.
this en JavaScript y c贸mo ha cambiado el uso de this a partir de ES6?This es una palabra reservada que hace referencia al objeto due帽o del m茅todo.
Su uso ha cambiado en particular con la implementaci贸n de las arrow functions las cuales no tienen su propio valor de
thissino que hacen referencia al valor dethisdentro del contexto de ejecuci贸n que la contiene.
Una funci贸n declarada dentro de otra funci贸n y que tiene acceso a su propia scope y a la de su funci贸n padre
Un espacio de nombres es un "contenedor" para un conjunto de identificadores, funciones, m茅tudos, etc. El espacio de nombres global es el "mas exterior", correspondiente al objeto global.
- Orientado a Objetos, soportado a trav茅s de la herencia por prototipos
- Funcional
La programaci贸n funcional produce programas a trav茅s de la composici贸n matem谩tica de funciones y evita compartir su estado, as铆 como mutaci贸n de datos y evitar efectos secundarios (funciones puras).
Herencia por clases: Las instancias (objetos) heredan de clases (como un plano o descripci贸n de la clase) y crean relaciones sub-clase: taxonom铆as de jerarqu铆as de clases. Las instancias normalmente se instancian usando funciones contructor con la palabra reservada
new. La herencia por clases puede o no usar la palabra reservadaclassde ES6.Herencia por prototipos: Las instancias heredan directamente de otros objetos, normalmente a trav茅s de funciones que implementan el patr贸n de dise帽o factory o usando
Object.create(). Las instancias pueden estar compuestas de muchos objetos diferentes, permitiendo "herencia selectiva".
Un objeto puede apuntar a otro y heredar todas sus propiedades. Todos los objetos en JavaScript tienen un enlace al objeto prototipo y cuando se quiere acceder a la propiedad de un objeto, dicha propiedad no s贸lo se buscar谩 en el objeto sino tambi茅n en su prototipo, el prototipo de su prototipo y as铆 sucesivamente hasta que se encuentre o se llegue al fin de la cadena de prototipos.
El two way data binding significa que los campos en la UI est谩n enlazados din帽amicamente a un modelo de datos de tal forma que cuando la UI cambia, el modelo de datos tambi茅n cambia, y viceversa. Por ejemplo el framework Angular
El flujo de datos unidireccional significa que el modelo es la 煤nica fuente de verdad. Los cambios en la UI mandan un mensaje de cambio de datos en el modelo (o store, en React) generado por el usuario. 脷nicamente el modelo tiene acceso a cambiar el estado de la aplicaci贸n. Esto hace que los datos fluyan en una 煤nica direcci贸n. Por ejemplo el framework React.
Una arquitectura monol铆tica significa que la aplicaci贸n est谩 escrita como una unidad de c贸digo con cohesi贸n, cuyos componentes est谩n dise帽ados para trabajar en conjunto, compartiendo el mismo espacio de memoria y recursos.
Pros de arq. monol铆tica: Su mayor ventaja es que la mayor铆a de aplicaciones t铆picamente tienen un gran n煤mero de responsabilidades compartidas, como generaci贸n de bit谩coras (logging), establecer l铆mites de consumo (rate limitting) y funciones de seguridad, como auditor铆as y protecci贸n contra DOS.
Contras de arq. monol铆tica: Los servicios monol铆ticos tienden a estar altamente acoplados y a medida que la aplicaci贸n evoluciona, se vuelve dificil aislar los servicios con objeto de brindarles indenendencia en cuanto a escalabilidad o mantenibilidad. Tambi茅n son mas dif铆ciles de entender porque normalmente tienen muchas dependencias y efectos secundarios que no son tan obvios cuando se observa un servicio o controlador particular.
Pros de arq. microservicios: Normalmente est谩n mejor organizadas puesto que cada microservicio tiene un trabajo espec铆fico y no le deber铆a de afectar el trabajo de los otros componentes. Est谩n bajamente acoplados y son m谩s f谩ciles de componer y reconfigurar para diferentes prop贸sitos. Tambi茅n suelen tener mejor desempe帽o dependiendo de c贸mo est茅n organizados puesto que es posible (y com煤n) que est茅n aislados y se puedan escalar independientemente del resto de la aplicaci贸n.
Contras de arq. microservicios: Mientras se desarrolla una arquitectura de microservicios probablemente se vislumbren temas o asuntos que no estaban considerados originalmente en la etapa de dise帽o. Tambi茅n se crea un esfuerzo adicional para separar los m贸dulos de acuerdo a responsabilidades, o encapsular responsabilidades compartidas en otra capa de servicio por la cual haya que rutear todo el tr谩fico. Normalmente los microservicios se despliegan en su propia VM o contenedor, causando mas trabajo en administrar dicha virtualizaci贸n u orquestaci贸n de cluster contenedores.
La programaci贸n s铆ncrona significa que, exceptuando condicionales y llamadas a funciones, el c贸digo es ejecutado secuencialmente de arriba hacia abajo, generando bloqueos en tareas de larga ejecuci贸n, como peticiones a red o lectura/escrituda a disco.
La programaci贸n as铆ncrona significa que la ejecuci贸n se realiza en un bucle de eventos (event loop). Cuando se realiza una operaci贸n bloqueante (como las mencionadas anteriormente) la petici贸n inicia y el c贸digo sigue ejecut谩ndose sin generar bloqueos. Cuando la respuesta a la petici贸n est谩 lista, se genera una interrupci贸n lo cual ocasiona que se ejecute un manejador de eventos (event handler), donde el flujo de control contin煤a. De esta forma, un 煤nico hilo de un programa puede manejar muchas operaciones concurrentes.
Las UI son as铆ncronas por naturaleza, lo que significa que el servidor trabaja de la misma manera, esperando la respuesta de la red en un bucle y aceptando m谩s peticiones entrantes mientras que se atiende la primera.
Esto es importante en JavaScript porque incrementa el desempe帽o en el servidor y es un comportamiento "natural" en el c贸digo del cliente.
