Curso de Javascript Completo do iniciante ao mestre - Parte 2

Seção 11 - Básico de objetos

Aula 151 - Introdução (Básico de objetos)

Objetos são tipos de estruturas de dados que armazenam coleções de chave / valor. Um array aramazena dados ordenados, enquanto objetos armazenam dados não ordenados.

Aula 152 a 153 - propriedade e métodos de valores primitivos? Como é possível? / Operador new

Relembrando os objetos podem ser criados com a sintaxe literal:

const nome_do_objeto = { atributo: valor }

Ou com a sintaxe formal:

const nome_do_objeto = new Object()

No JS alguns primitivos são convertidos para objetos de forma transparente para poder receber alguns métodos, como por exemplo as strings que podem receber a propriedade lenght. Nesse caso o JS converte a string em objeto e após retornar seu valor descarta o objeto. Diferente de um objeto string criado com a sintaxe:

const nome_da_string = new String('valor_da_string')

O operador new é responsável por criar os objetos.
Um exemplo muito utilizado é com o objeto Date(). O objeto Date() quando utilizado semo o perador new retorna uma string com a data atual. Já quando criamos uma variável com o operador new Date() instanciamos o objeto Date() que por sua vez possui diversos métodos como getDate, getDay, getMonth, etc.

Aula 154 - Revisitando: valores vs referência

No JS dados primitivos são armazenados por valor, e todos os demais são armazenados por referência (objetos, arrays, funções).
Quando passamos uma valor primitivo para uma função e fazemos uma operação com esse valor não estamos mudando o valor mas sim recuperando o valor sem alterá-lo. Já quando manipulamos um array através de um método como o push a alteração feita na função também altera o valor externo pois estamos manipulando a referência do array e portano o objeto externo de forma indireta.
O mesmo ocorre com objetos. Por esse motivo se tentarmos fazer uma comparação entre dois objetos vazios vamos ter um retorno falso, pois cada objeto é na verdade uma referencia diferente na memória.
Podemos verificar isso também comparando strings primitivas com o objeto String. No primeiro caso se tivermos valores iguais teremos retorno true. Já em objetos String mesmo que o valor interno seja o mesmo a comparação retornará false.

Aula 155 - Objetos customizados

Podemos criar quantos objetos literais com a mesma estrutura queiramos, entretanto caso precisemos incluir uma nova propriedade, precisaremos fazer a inclusão manulamente em todos os objetos.
O operador this quando utilizado faz referencia sempre a quem está chamando o método ou função. Por exemplo se utilizamos uma função externa como método em um objeto quando chamanos a função pelo objeto o this faz referência ao próprio objeto. Já quando executamos a função no código externo o this faz referência ao 'window' ou ao elemento externo que chama a função. Isso não ocorre com arrow functions pois nesse caso o this é fixo e não se altera por onde se está chamando.
A partir do ES5 em um objeto quando temos o atributo e o valor com os mesmos nomes pordemos omitir o valor, desse modo:

{ nome: nome }
é o mesmo que:
{ nome }
Com tudo isso quando precisamos alterar a estrutura de objetos o ideal é que tenhamos uma função construtora que irá definir os atributos e métodos que todas as instancias desse objeto podem executar.

Aula 156 - Funções construtoras

Para criar uma função construtora devemos utilizar o operador new. Por convenção as funções construtoras em JS devem iniciar com letras maiúsculas.
Nas funções construtoras declaramos os atributos com o operador this.nome_do_atributo.
Declarados os atributos na função construtora devemos instanciar o objeto com o operador new.
Quando queremos proteger nosso objeto de alterações podemos mudar a definição dos atributos substituindo o operador this por let. Com isso as variáveis passam a ter escopo local na função construtora e deixam de ser acessíveis como atributos. Nesse caso por padrão declaramos o atributo precedido por '_nome_d0_atributo'.
Feito isso, para podermos ver o conteudo de um atributo precisamos criar uma função que retorne o valor desse atributo. Fazemos isso por padrão criando uma função getter. No exemplo foi criada uma função changeName() que altera o valor do atributo _name. Essa função tem o mesma idéia de um setter, só mudou o nome.

Aula 157 - Obrigar o uso do operador new nos nossos construtores

Quando não utilizamos o operador new para instanciar um objeto atribuimos à variável um valor undefined. Isso ocorre porque a função construtora não tem nenhum retorno. Entretanto como a função tem atributos com o operador this, esses atributos passam para o escopo global e consequentemente passam a existir no escopo global.
Para prevenir a chamada do método construtor sem a utilização do operador new utilizamos o 'use strict na função construtora o que vai gerar um erro caso haja a chamada da função sem ele.

Aula 158 - Desafio: Lista de tarefas com métodos construtores

Aula 159 - Resolução: desafio

A criação do método construtor foi igual a que eu fiz.
Já para a criação da variável arrInstancesTasks foi utilizado o método map(). Para extrair os atributos do array existente foi utilizado o destructuring passando os valores dos atributos. Eu utilizaei o forEach() e ele não retorna nada. Talves por isso que tive tanto trabalho para que a conversão funcionasse. Como o método map() tem uma função com retorno esse retorno vai direto para o método, da seguinte forma:
const arrInstancesTasks = arrTasks.map((task) => {
const { name, completed, createdAt, updatedAt } = task;
return new Task(name, completed, createdAt, updatedAt);
});

No mais tudo ficou igual, fora que eu criei uma variável antes de instanciar as novas tasks e poderia ter instanciado diretamente como parâmetro do método push().

Aula 160 a 161 - Desafio: Nome privado / Resolução: desafio

Conversão da variável name para private e criação de métodos setters e getters.

Seção 12 - Strings

Aula 162 - replace(), replaceAll(), indexOf(), lastIndexOf() includes(),

O método de string replace() substitui o primeiro valor definido pelo segundo para a primeira ocorrencia na string. Esse método mantém o valor original da string (menos no caso de reatribuirmos o valor para a variável).
O método replaceAll() tem pouca utilidade pois é pouco suportado (não é suportado nem pelo Run Code do VSCode). Ele substitui todos os valores pelo valor definido, tarefa que pode ser realizada utilizando Regular Expressions.
O método indexOf() retorna o índice do primeiro caractere da string selecionada. Já o lastIndexOf() retorna o último valor encontrado na string. Caso a seleção não exista retorna '-1'.
O método includes() verifica se existe o valor selecionado na string verificada e retorna um booleano (true / false). Ele pode facilmente ser substituido pelo indexOf() poi podemos fazer um teste utilizando esse método. Caso indexOf() seja '>= 0' o retorno também será um booleano (o retorno de indexOf() quando não existe o valor é '-1').

Aula 163 a 165 - slice(), substring() / toLowerCase() e toUpperCase() / valueOf()

Os métodos slice() e substring() servem para 'cortar' a string do índice 'a' ao índice 'b'. Caso passemos apenas um valor eles fazem o corte a partir desse indice até o fim da string. A diferença entre eles é que o slice() aceita valores negativos (contando de trás para frente) e o substring() em caso de números negativos considedra o valor como '0'. Já no caso de valores passados na ordem inversa (do maior para o menor) o substring() consegue tratar, já o slice() não.
Os métodos toLowerCase() e toUpperCase() convertem as letras da string para minúsculas e maiúsculas.
O método valueOf() retorna o valor da string (utilizada quando a string for um objeto, caso contrário retorna a string normal). tem o mesmo efeito em objetos que o método toString().

Aula 166 a 168 - trim() / padStart(), padEnd() e desafio de última hora / Resolução: desafio

O método trim() remove espaços e pulos de linha no início e no fim da string. Temos também os métodos trimStart() e trimEnd() que remove apenas os espaços no ínicio ou no fim da string.
Os métodos padStart(n) e padEnd(n) completam os espaços da string para que ela finque com n caracteres.

Aula 169 a 170 - startsWith(), endsWith() / charAt()

Os métodos startWith() e endsWith() verificam se a string começa ou termina com os valores passados como parâmetro. Podemos passar como segundo parâmetro a partir de qual posição iniciar a busca (sendo que o endsWith() faz a busca de trás para frente).
O método charAt() retorna o caractere que está no índice passado por parâmetro. Esse método faz o mesmo que se selecionarmos a string e passarmos o índice entre '[]'. Já o método charCodeAt() retorna o código UniCode do caractere na posição selecionada. Por fim temos a propriedade legth que retorna o tamanhoi em caracteres da string.

Aula 171 -

Resolvi o exercício do meu jeito no arquivo main.js.
Na resolução proposta, uma das coisas iniciais foi a escolha da utilização do textContent ou do innerText (tinha utilizado essa propriedade no meu). Se utilizarmos o textContent na hora de contar os caracteres ele contará os espaços da formatação do editor portanto não podemos utilizar essa propriedade.
Cometi um erro no meu código pois não armazenei os dados das parágrafos individulamente, portanto eles estavam sendo sobreescritos pelo último parágrafo armazenado.
Outra informação nova foi a utilização da propriedade classList.toggle(). Essa propriedade inclui determinada classe caso ela exista e exclue em caso contrário.
Em seguida foi criada a função de listener para o botão. Em princípio recebemos apenas o evento e fazíamos o toggle das classes. Entretanto para recuperarmos os parágrafos no array que foi criado passamos a receber como parametro também o parágrafo e seu índice. A partir disso o operador this passa a se referir ao Window e não mais ao button. Com isso precisamos utilizar o e.currentTarget para acessar o evento do button.

Aula 172 a 174 - Desafio: Formatar nome / Resolução: desafio / split()

Formatar nome separando o sobrenome do nome principal. Minha resolução foi ok. Preciso me acostumar a utilizar dois return na mesma função.
Podemos entretanto utilizar o método split(). esse método cria um array separando a string de acordo com o valor passado como parametro. Por fim refizemos o desafio utilizando o método split()

Seção 13 - Number

Aula 175 a 178 - toPrecision(), toFixed(), toExponential() / toString(), toLocaleString() / MAX_VALUE, MIN_VALUE / isNaN()

O método toFixed() define quantas casas decimais queremos no número. O retorno é uma string. Já o método toPrecision() define a quantidade de dígitos que queremos que seja apresentado pelo número. Caso o número de dígitos seja menor que o número de origem, ele fará o arredonadamento e se necessário converterá para exponencial. Caso seja maior ele completará o número com '0' no lado decimal. Ness método o retorno também é uma string.
O método toExponential() converte o número para exponencial, sempre com um dígito antes do ponto decimal.
O método toString() quando utilizado com números retorna o número no formato string entretanto ele pode receber um parâmetro que converte a base do número de acordo com o valor desse parâmetro.
O método toLocaleString() permite converter a formatação do número para a localidade definida nos parâmetros. Pode inclusive receber parâmetros {style: 'currency'} de moeda e retornar o número como moeda local.
Os atributos MAX_VALUE e MIN_VALUE retornam o maior e o menor número possível de ser representado pelo sistema. Ambos são atributos do objeto Number. Para entender algumas diferenças entre o objeto Number podemos analisar o método isFinite(). Esse método existe no objeto Number mas também existe no objeto global. Assim quando utilizamos o isFinite() no navegador ele pode retornar true tanto para um número como para uma string número. Já quando utilizamos o método no objeto Number o retorno para uma string tipo número será false.
O método isNaN() verifica se um valor pode ser convertido para número ou se vai retornar 'NaN' durante a conversão.

Seção 14 - Math

Aula 179 - min(), max(), pow(), round(), sqrt(), cbrt(), random()

Métodos do objeto Math. Os métodos Math.min() e Math.max retornam o menor e o maior valores em uma coleção de números.
O método Math.round() arredonda o número de acordo com o valor médio. Já os métodos Math.floor e Math.ceil() arredondam sempre para baixo ou para cima.
O método Math.pow(n, p) calcula o numero 'n' elevado a potencia 'p'. Já o método Math.sqrt() retorna a raiz quadrada de um número e o Math.cbrt() retorna a raiz cúbica.
O método Math.random() retorna números aleatórios entre '0' e '1'.

Aula 180 - sorteia.js

Execução de um sistema de sorteio de numeros aleatórios definindo um número inicial e um final e se o retorno é inteiro ou flutuante.

Aula 182 a 183 - Desafio: Background randômico / Resolução:desafio

Alterar o backgroud de uma página a cada recarga. Utilizamos a função sortear mas também podemos fazer o cálculo diretamente.

Seção 15 - Date

Aula 184 - Introdução

O objeto Date() cria uma instancia de Date que representa um único momento baseado no valor de tempo em milisegundos contado a partir de 01/01/1970 (UTC). O objet Date() não tem expressão literal, ou seja exige o operador new.
Os métodos de Date() são vários. Entre eles: getDay()(dia da semana iniciado em 0 'Domingo'), getDate()(dia do mes), getMonth()(iniciado em 0 'Janeiro'), getTime()(timestamp), getFullYear(), getHours(), getUTCHours().
O objeto Date() possui alguns métodos estáticos que só podem ser acessados pelo construtor ( Date.UTC(), Date.now(), Date.parse().
Podemos redefinir datas com os métodos set... que possuem vários dos métodos get...

Aula 185 - toString()

Temos também diversos métodos para retorno de strings de data em vários formatos, entre eles: toString(), toDateString(), toISOString, toLocaleString(), toLocaleDateString(), toUTCString(). Alguns desses métodos podem receber alguns parametros como localidade e tipo de retorno (meses / dias por extenso). Note que os retornos podem variar de acordo com o intepretador (por exemplo o RunCode não retorna os valores corretos, já o Chrome formata bem). Existem bibliotecas que normalizam esses dados inclusive no NodeJS. Para mais informações verificar a documentação em MDN Web Docs - Date

Aula 186 - Exercício proposto: contagem regressiva

Para recuperar os valores da data utilizamos o destructuring e o split() para separar os valores em variáveis independentes. Em seguida retornamos como new Date() os valores das variáveis recuperadas criando assim uma variável com a data fixa. Dessa data fixa recuperamos o valor do timestamp.

Aula 188 - Desafio: dias para o seu aniversário

Executei a resolução do desafio porém a função de cálculo já estava pronta. Na parte de exebição na tela fiz de forma um pouco diferente mas o resultado foi o mesmo. Eu utilizei o destructuring e fiz a inclusão do parágrafo com createElement e appendChild o professor fez com os índices do array e com innerHTML passando o <p> de forma literal.

Aula 189 - Resolução: desafio

Para a função de calculo do tempo até o aniversário primeiramente foi zerado os valores de hora, minuto, segundo e milissegundo para evitar que os valores dessas variáveis influenciem no valor da conta que é apenas em dias.
Podemos utilizar um sintaxe diferente para a função getTime(). Basta colocar o sinal de '+' antes da variável que se quer atribuir o valor.

Seção 16 - Intervalos

Aula 190 - Introdução

Os métodos responsáveis por manipular intervalos são setTimeout(fn, ms), setInterval(fn, ms), clearTimeout(id) e clearInterval(id).
O setTimeout() agenda uma função para ser executada uma única vez. Já o setInterval() agenda uma função para ser executada a cada x tempo.
Tanto o setInterval(), quanto o setTimeout() recebe uma função que é executada no tempo definido, sendo que o primeiro executa repetidamente até que o clearInterval() seja executado. Os dois métodos de clear devem receber um id como parametro que é a identificação do método a que ele se refere. Essa id pode ser declarada como uma variável que recebe os métodos.
Podemos perceber com o setTimeout() e o setInterval() a característica do JS de ser single-thread pois ele atrasa a execução dos métodos de tempo em razão de execuções mais custosas ao processador, ou seja, se programamos um setTimeout() para ser executado em x ms esse processo será colocado na fila para execução. Se houver um processo sendo executado nesse momento o processo chamado pelo setTimeout() só será executado após a conclusão do processo em execução.

Aula 191 - Desafio: Save the date

O desafio consiste em automatizar a contagem regressiva da página de Save the Date. Fiz no meu modelo nesta aula. Basicamente inclui o método setInterval() executando o cáculo do tempo. O meu problema foi que deixei apenas o cálculo dentro do setInterval() o que fazia com que houvesse um delay na carga do contador na tela. Posteriormente na resolução o professor em um primeiro momento faz o mesmo, duplicando o código de cálculo fora e dentro do setInterval(). A única diferença no meu caso foi que retirei o código de fora do setTime() por isso o delay. Em seguida refatorei o meu código com as explicações da resolução. A refatoração consiste em criar uma função para fazer os cálculos e executá-la duas vezes. Uma inicial e a outra como parâmetro do setInterval().

Aula 192 - Resolução: desafio

Resolução do exercício anterior com o código do professor.

Seção 17 - BOM

Aula 193 - Introdução

O BOM é um objeto que oferece propriedades e métodos para interagir com o browser.
As propriedades innerHeight e innerWidth mostram a altura e a largura da área ativa do browser já o outerHeight e o outerWidth mostram a altura e largura total da janela do browser na tela. Uma diferença interessante é que quando aplicamos zoom no browser as propriedades inner reduzem de tamanho, enquanto a propriedade outer se mantém igual.
Já as propriedades screenX e screenY se referem a posição do browser em relação a tela, sendo que caso a janela do browser estiver posicionada fora dos limites horizontais ou verticais da tela seus valores serão negativos.

Aula 194 - History

A propriedade history acessa o histórico de navegação do browser. Ele possui a propriedade length que mostra o valor de dados armazenados no history e métodos como back() e forward() que permitem avançar e retroceder no histórico.

Aula 195 a 197 - Location / Navigator / Screen

O objeto location possui diversos atributos que retornam valores da URL como href, hash, search, protocol, host.
Temos também alguns métodos como assign(), reload() e replace() que mudam ou recarregam a URL.
Já o objeto navigator permite verificar dados sobre o ambiente de navegação. Algumas propriedades são userAgent, plugins, cookieEnabled, platform.
O objeto screen mostra propriedades do monitor como availWidth, availHeigth, width, heigth.

Aula 198 - Métodos de window

O Window tem alguns métodos de interação próprios. Aguns deles são alert(), confirm(), prompt(), open(), close(), print().
Os três primeiros métodos enviam mensagens na tela.
O método close() fecha janelas, porém apenas as abertas pelo método open(). Quando abrimos uma página com o método open() do Window ativamos o atributo opener que retorna o objeto Window da página que abriu a atual. Esse atributo tem acesso inclusive a variáveis e funções da página de origem. Podemos passar alguns parâmetros com o método open() como width e height da janela que será aberta.
O método print() abre a opção de impressão da página.

Aula 199 - scrollTo() e scrollBy()

Os métodos scrollTo() e scrollBy() permitem o controle de rolagem da tela. O método scrollTo() move para uma posição, já o scrollBy() move no valor 'x', 'y'.
Temos os atributos pageXOffset e pageYOffset que mostram os valores de rolagem na tela.

Aula 200 - Eventos

Eventos do browser: load, DOMContentLoaded ocorrem no carregamento da página com a diferença de que o load é disparado quando toda a página é carregada, inclusive os acessórios. O DOMContetnLoaded é disparado quando a árvore da página local é carregada.
Temos os eventos resize, que é disparado ao redimensionar a janela e o scroll que é disparado ao se rolar a tela.
Outros eventos são unload e beforeunload que são pouco utilizados pois são 'bloqueados' pelos navegadores devido ao mau uso.

Aula 201 - Desafio: scroll

Aula 202 - Resolução: desafio

Na resolução o que foi feito de diferente é que quando fazemos o listener de métodos como o scroll a cada movimento na barra de rolagem temos uma manipulação nas classes do DOM. Para evitar isso implementamos um teste para verificar se a classe está adicionada na tag <body>. Caso ela esteja, não há necessidade de se fazer nada até que esse estado mude. Essa verificação é feita monitorando o HTMLCollection[0] recuperado pelo getElementsByClassName().

Seção 18 - Estilos

Aula 203 - Introdução

Podemos definir estilos de diversas formas. O estilo definido no <head> do documento não tem prioridade sobre o elemento inline. Se definirmos a propriedade no <head> como !important ela passa a sobreescrever a propriedade inline. Entretanto no console do navegador a propriedade que será apresentada será a inline, mesmo que isso não se reflita na tela. A outra forma de definir estilos é com a propriedade JS style, lembrando que nesse caso as propriedades com nomes compostos devem ser escritas em camel case.
Temos também o objeto classList que tem os métodos add(), remove() e toggle(). Podemos adicionar nomes de classe a elementos HTML com a propriedade classList.add(). Esse método adiciona uma classe às classes existentes. Podemos também acessar a propriedade className porém neste caso sobreescrevemos as classes adicionadas anteriormente.
Outro método disponível é o getComputedStyle(). Ele é um método do Window e exige que se passe a tag que se quer verificar como parâmetro.
Tamanho e posição dos elementos no documento.
Podemos recuperar a posição e o tamamnho dos elementos em relação ao documento com propriedades offsetWidth, offsetHeight, offsetTop e offsetLeft.
Posição em relação a viewport.
Para essa posição utilizamos o método getBoundingClientRect() que apresenta diversas medidas em relação ao ponto '0, 0' do viewport. Lembrando que esse método se refere ao elemento que o invoca.

Aula 204 - Exercício proposto: Marcar link ativo no scroll

Implementar a ativação com cores do link de acordo com a posição do scroll da tela.

Aula 205 - Resolução: link ativo no scroll

Na resolução do exercício o professor utiliza o spread operator para espalhar os itens dos <h2> e utiliza o método map()para recuperar cada um individualmente. Uma das principais diferenças em relação a minha resolução é que ele utiliza como referência o atributo href das tags <a>. Como esse valor é a representação dos id das tags <h2> o valor passado para o querrySelector() é a representação de uma id( #nome_da_id). Para cada elemento é feita a recuperação do atributo top do método getBoundingClientRect(). Esse valor fica negativo conforme o rolamento da tela para cada elemento pois o que é monitorado no evento scroll é a própria tag <h2>.
Com isso é criada uma função para verificar se o valor top é < 0. Caso seja a função retorna o valor do length do array de <a> -1 (que é o índice da tag desejada).
Por fim o valor da posição em que é feita a troca da classe actived (que controla qual <li> está ativa) mudou de 0 para innerHeigth / 2 ou seja metade da posição na tela.

Aula 206 a 207 - Exercício proposto: Background paralax / Resolução: Background paralax

Neste exercício primeiramente vamos monitorar o 'scroll' do window. Em seguida selecionamos o elemento a ser manipulado através do seu atributo.
O modo que movimentamos a imagem de fundo é com a propriedade backgroundPosition. Recuperamos o valor da posição 'Y' em relação ao topo com o método getBoundingClientRect().top.
Esse valor é multiplicado pela velocidade que queremos que o efeito ocorra na tela e é passado como novo valor da posição 'Y' para o backgroundPosition

Aula 208 - Desafio: Mostrar imagem no scroll

Resolução do desafio. Utilizei os mesmos métodos do paralax e criei um setTimeout() para fazer o delay recuperado dos atributos dos elementos.

Aula 209 - Resolução: Desafio

A primeira coisa diferente qu o professor fez foi definir a posição do elemento hna tela de modo dinamico. Eu tinha deixado um valor fixo.
Na função que exibe as imagens na tela, foi criada uma variável para recuperar o valor do delay de cada elemento. Em seguida foi utilizado o setTimeout() como eu utilizei.
Como nessa execução foi criada uma variável para o delay a function do setTimeout() faz todas as operações de classes. Essa função inclui a classe definida no atributo data-addclass-on-scroll (que no caso é a classe 'show'). Em seguida removemos os atributos dos elementos pois com isso podemos fazer um teste. Caso não tenha mais nenhum atributo nos elementos podemos desativar o listener. Essa ação é para deixar a página mais performática.

Seção 19 - Funções - parte II

Aula 210 - Introdução

Nesta seção vamos estudar Arrow functions, Métodos de objetos, Closure, call(), apply() e bind(), Encadear métodos, Reveal pattern, factory e constructor

Aula 211 - arrow functions

A sintaxe de arrow function é:
const nome_da_função = (param) => {
corpo da função
return valor_a_ser_retornado
}

Quando temos apenas um parâmetro e o retorno, sem nenhum código adicional podemos simplificar:
const nome_da_função = param => valor_a_ser_retornado

Quando retornamos um objeto devemos passá-lo no return, porém podemos também simplicar da seguinte forma:
const nome_da_função = param => ({
atributo_do_objeto: valor_do_atributo
})

Diferente das function expression que podem ser invocadas antes de serem declaradas, as arrow functions só podem ser referenciadas após sua declaração.

Aula 212 - Escopo léxico vs dinâmico

Quando temos variáveis de escopo global, elas são acessíveis em funções. Entretanto quando temos parametros, mesmo que com o mesmo nome de uma variável global o parametro não é a mesma coisa que a variável. E quando passamos uma variável de escopo local com o mesmo nome de uma variável de escopo global a variável local terá precedencia na função.
Uma das diferenças de arrow functions para function expressions é na captura do this de cada uma. O browser tem uma abordagem específica e o this é o objeto global, no caso o Window, mas no NodeJS o retorno do this em uma arrow function é um objeto vazio enquanto em uma function expression é o objeto global.
Podemos verificar a mudança no this quando utilizamos o setTimeout(). Quando utilizamos uma function expression no setTimeout e retornamos o this esse deixará de se referir ao objeto e passará a se referir ao escopo global (na function expression o this é dinamico). Já quando utilizamos uma arrow function no setTimeout o retorno é o próprio objeto pois o escopo nesse caso é estático.
Já em um listener o this de uma function expression é o objeto do evento, enquanto na arrow function é o objeto global.
Uma outra observação no listener é comparar o this local com o global. Quando temos uma function expression os dois são diferentes já na arrow function os doi são iguais.

Aula 213 - Métodos de objetos

Podemos criar métodos (funções) em objetos de várias formas. Podemos declarar o nome do método seguido da criação de função com o function, podemos atribuir uma função externa e atribuir ao método e podemos simplesmente criar o método declarando seu nome seguido de '()' e com as tarefas a executar entre '{ }'.
Não podemos entretanto colocar uma função para ser chamada dentro de um método, pois nesse caso perdemos a referencia do this, ou seja, seu escopo fica dentro do método que o chamou e não dentro do objeto.

Aula 214 - apply vs call vs bind

Quando como no exemplo acima perdemos a referencia do this, podemos utilizar a função call() para alterar o escopo do this.
Quando criamos uma função o escopo do this é global. Com isso quando declaramos uma variável de escopo global com o operador var essa variável é acessível (pelo Chrome não pelo NodeJs, isso porque no Chrome o objeto global é o Window, diferente do NodeJs onde o objeto global é apenas 'global'). pelo this. O mesmo não acontece quando utilizamos o let ou o const.
Quando utilizamos a função call() e passamos um objeto, esse objeto passa a ser o this na função executada. Podemos em seguida passar os parâmetros da função normalmente.
O mesmo ocorre com o apply() entretanto neste caso os parâmetros devem ser passados como um array. Nesse caso podemos utilizar a função call() e utilizar o spread operator(...) para espalhar o array dos parâmetros.
Podemos atribuir um novo objeto this para uma função utilizando o método bind(). Nesse caso criamos uma nova função que irá receber a função principal com o bind() do objeto que irá assumir o escopo do this. Dessa forma quando chamamos a nova função ela terá como escopo o objeto passado para a primeira função pelo bind().

Aula 215 - closures

Closures é a capacidade de uma função exergar as variáveis que estavam ao redor dela no momento que ela foi declarada.
Quando criamos uma função dentro de outra função ela consegue aproveitar as variáveis criadas na primeira função, mesmo que essa já não exista mais no escopo. Note que se a variável estivesse dentro da função interna ela sempre seria reiniciada, o que não ocorre quando ela está no escopo externo.

Aula 216 - Encadeamento de métodos

Podemos encadear métodos desde que façamos referencia ao objeto principal e retornemos esse objeto. Esse aula é um pouco difícil de explicar, mas basicamente se retornarmos os objetos com this podemos encadear um método a outro pois eles vão sempre acumular o resultado do método anterior.

Aula 217 - reveal pattern

O revel pattern é uma técnica de programação que utiliza técnicas de encadeamento, closures e funçõess auto executáveis para proteger o escopo de variáveis em aplicações maiores. Era mais utilizada antes do ES6 quando só se declarava variáveis com var para evitar que o escopo das variáveis vazasse para fora do escopo da função que elas deveriam ficar.

Aula 218 - Factories Functions

Function Factory são funções que retornam objetos. São muito parecidads com métodos construtores.
Criamos com Function Factories objetos como nos métodos construtores onde retornamos o objeto construtor e os valores que queremos processar nos métodos. Acredito que a única diferença em relação aos construtores é a utilização do return.

Aula 219 - Iniciando com getters

Quando temos um objeto e criamos um método, podemos tranformá-lo em um atributo utilizando a palavra reservada get.
Quando passamos o operador get para um método transformamos ele em um atributo, assim ele pode ser chamado como uma propriedade (sem utilizar o '( )'). Esse método deixará de ser uma função para ser um getter.

Aula 220 - constructor

Assim como a function factory os constructors são funções que retornam um objeto que é um molde desse objeto. A principal diferença entre eles é que nos constructors nós devemos utilizar o operador new.
Quando criamos funções contrutoras utilizamos letras maiúsculas para o inicio do nome das construtoras.
A primeira diferença dos constructors é que os atributos e métodos são declarados precedidos pelo operador this e diferente das factories não precisa do return.

Aula 221 - rest operator

O rest operator faz o inverso do spread operator ou seja ele recupera valores de argumentos e agrupa em uma estrutura semelhante a um array. O simbolo é o mesmo '...' e podemos utilizá-lo quando recebemos diversos parâmetros em uma função. Nesse caso ao declararmos o parametro da função com o operador ele agrupará os valores recebidos na chamada da função criando um array com esses valores.
O rest operator deve ser utilizado como último parâmetro.

Seção 20 - Introdução a Orientação a objetos

Aula 222 - Introdução

Orientação a objetos é um paradigma de programação baseada na interação entre objetos. Um objeto é uma unidade que une dados (propriedades) e suas funções (métodos). Em resumo é um conjunto de valores armazenados em '{ }'. Nos objetos temos uma coesão, os dados e comportamentos ficam agrupados.
A diferença entre o OO em JS e as outras linguagens é que em JS utilizamos prototypes enquanto nas outras linguagens temos classes. Uma classe é uma especificaçao para a criação de objetos. Cada objeto é único e pode ser modificado, entretanto uma mudança na classe altera todos os objetos. Cada novo objeto é uma instancia da classe. O protótipo ou classe não são utilizados o que utilizamos são sempre os objetos criados a partir da classe.
A herança é a capacidade de reutilizar os protótipos para outros tipos de classes que darão origem a objetos com atributos e métodos diferentes. Na OO utilizamos classes abstratas que são genéricas e que vão fornecer propriedades por herança para classes mais específicas ou concretas. As classes abstratas não podem ser instanciadas, elas apenas devem oferecer as classes específicas seus atributos e métodos, para que essa sim seja instanciada e transformada em objeto.

Aula 223 - ES5 vs ES6

Em ECS2015 a class é apenas um 'açucar sintático' para funções construtoras, ou seja não são classes como em linguagens de OO assim como o extends é basicamente o prototype.

Aula 224 - prototype

O JS possui um objeto principal ('Object') que possui seu prototype. Quando chamamos um método em um objeto, ele irá procurar em toda a árvore de objetos até chegar no objeto principal.
Quando criamos uma função ela também terá seu prototype que herda os métodos do Object.
A propriedade prototype de uma função construtora é um objeto. O objeto criado a partir da função construtora tem uma propriedade __proto__ que possibilita o acesso ao objeto prototype da função construtora.
Podemos adicionar métodos e propriedades diretamente no prototype de uma função construtora. Essas funções são acessíveis para os objetos instanciados. Podemos criar atributos e métodos do tipo prototype utilizando o nome da função construtora seguido da propriedade prototype e do nome do argumento ou método.
Podemos criar métodos para objetos nativos do JS. É o que acontece quando utilizamos o polyfill ou seja funções que adicionam funcionalidades para versões anteriores do ESC2015. Um exemplo da criação de métodos diretamente no prototype de um objeto nativo do JS pode ser visto na documentação do MDN do método some(). Aqui podemos ver que o polyfill cria uma função que executa a rotina do some() diretamente no prototype do construtor Array.

Aula 225 - Momento de reflexão

Pelo que foi possível entender no exemplo que devemos analisar o objeto sapo não é instanciado, assim o prototype funciona como uma função construtora. Nesse caso o this é o objeto global (ou Window). Assim devemos chamar o método pelo prototype da função construtora, utilizando o call() para passar o escopo do this para a variável 'sapo'. Note que se verificarmos a propriedade constructor dos objetos instanciados teremos como retorno o objeto construtor 'Animal'. Já com a variável 'sapo' temos como retorno o 'Object' (objeto global).

Aula 226 - Herança com prototype

Podemos criar heranças entre protótipos. Quando criamos um protótipo que deve utilizar métodos de outros protótipos podemos chamá-lo dentro do método que herdará os astributos e utilizar o método call().
Podemos também incluir a classe de quem se herdará as propriedades instanciando-a no protótipo da classe herdeira. Assim o constructor do objeto passará a ser a classe pai. Podemos, após a atribuição dos valores herdados voltar o constructor para o valor original reatribuindo esse construtor para o objeto.
Esses modos de lidar com construtores e protótipos é antigo e já não é mais utilizado. A seguir veremos como trabalhar com o ES6.

Aula 227 - hasOwnProperty, instanceof, isPrototypeOf, getPrototypeOf

Quando fazemos um loop for in nas propriedades de um objeto instanciado teremos como retorno todas as propriedades do construtor e as que foram incluidas na cadeia do protótipo.
Quando utilizamos o método hasOwnProperty() é verificado quais são as propriedades que fazem parte do objeto, ignorando as propriedades da cadeia de protótipo que não foram declaradas no objeto.
Podemos pesquisar se um objeto é instancia de um construtor com o método instanceof. Com esse método verificamos se o objeto é instancia de um construtor. Podemos fazer a pesquisa inversa com o método isPrototypeOf(). Neste caso verificamos se um construtor é protótipo de um objeto.
Podemos redefinir o construtor de um objeto para o construtor original com a propriedade Object.defineProperty() em que podemos passar o Objeto que será definido, o nome da propriedade e alguns atributos, entre eles o enumerable que caso seja 'false' não mostra o atributo como propriedade do objeto.

Aula 229 - class (ES6)

Com o ES6 podemos criar classes utilizando o operador class. Uma classe criada com o class exige uma função do tipo constructor() que irá armazenar os atributos do objeto. Já os métodos podem ser declarados diretamente sem precisar incluir no prototype da função construtora.
Verificando as propriedades dos dois tipos (class e função construtora) no console podemos observar que os dois possuem as mesmas propriedades, e tem as mesmas características.

Aula 230 - extends

Quando utilizamos funções construtoras suas variáveis ficam no escopo global. Isso permite que passemos valores para ela mesmo quando queremos que ela só seja acessada por outra classe. Por exemplo:
function Animal(tipo){
this.tipo = tipo
}
Animal('qualquer coisa')
tipo = 'qualquer coisa'
Ou seja, podemos passar uma valor para o 'tipo' mesmo sem ser pela classe filha. Para contornar esse problema podemos verificar se o this ou seja o 'tipo' passado por parametro é instanceof da classe 'Animal', ou seja, se ele é uma instancia (foi instanciado com o operador 'new') da função construtora 'Animal'. Caso contrário podemos retornar um erro.
Já quando utilizamos o operador class não conseguimos passar um 'tipo' sem ser através de um objeto instanciado com new. (Mensagem de erro padrão - Uncaught TypeError: Class constructor AnimalC cannot be invoked without 'new'). Isso só ocorre quando criamos a função com o operador class.
Como vimos anteriormente nas funções cconstrutoras, quando precisamos extender uma classe para outra precisamos faze-lo manipulando seu prototype.
Já com o ES6 utilizamos o operador extends para dizer que uma classe extende as propriedades de outra classe. Nesse caso a classe pai é chamada de super classe e deve ser referenciadda com o método super() logo depois do método constructor(). O método super()vai receber os valores que serão extendidos da super classe.

Aula 231 - Porque métodos ficam no protótipo?

Quando criamos funções construtoras podemos criar os métodos dentro do próprio construtor (this.método = function(){}), o que aparentemente é mais fácil do que criar os métodos no 'prototype' do construtor como fizemos no exemplo (Cachorro.prototype.método = function(){}). Entretanto quando criamos o método no construtor, para cada objeto instanciado teremos um novo método armazenado em memória, ao contrário dos métodos criados no prototype. Nesse caso os objetos apenas apontam para o método que está na memória.
Quando trabalhamos com a estrutura do ES6 utilizando class também ocorre isso caso criemos os métodos dentro do método constructor(). Já quando criamos o método fora do método constructor() esse método é armazenado no prototype.
Quando criamos construtores com Function Factories (ver aulas 218 e 219) também podemos criar os métodos no prototype. Fazemos isso utilizando a propriedade Object.setPrototypeOf(nome_do_objeto,{métodos_que_queremos_incluir}). Entretanto, nesse caso quando instanciamos dois objetos diferentes utilizando a mesma function factorie, mesmo os métodos estando no prototype teremos os métodos em posições diferentes da memória. Podemos verificar isso testanto:
obj1.__proto__ === obj2.__proto__
false

Para resolver isso precisamos retirar os métodos da function factory e criá-los como um função fora do seu escopo e apontar para essa função no método setPrototypeOf

Aula 232 - exercício proposto: Criar um polyfill para String

O fundamental desse desafio consegui fazer que é criar um protótipo para a String.
Na montagem da string de retorno testei várias formas mais complicadas. Não sei porque não tentei com o método join().
Outra coisa que é feita na resolução é recuperar o valueOf() da string, para garantir que será recuperado o valor quando a string estiver instanciada como um objeto do tipo String. Nos meus testes não precisei utilizar esse método.
Na minha primeira resolução não consegui substituir todas as letras, apenas a primeria ocorrencia pois utilizei o método slice().
Utilizando o método split() funcionou, porém tive problemas com o retorno do forEach(). Por algum motivo ele está criando o mesmo índice para as duas últimas ocorrencias do array.

Aula 233 - resolução: Criar um polyfill para String

Para a resolução primeiramente é feito um teste. Caso NÃO haja o método replaceAll() no prototype de String nós criamos o método. Isso serve para que o polyfill funcione efetivamente, ou seja, só libere a função caso o método não seja suportado pelo ambiente de execução.
Em seguida é criada a função que retorna o valueOf() da string recebida (no escopo ela é o this) para garantir que caso a string não seja primitiva (seja um objeto do tipo String) tenhamos o valor real da string.
Feito isso aplicamos o split() na string com o primeiro parâmetro e o join() com o segundo.
Para finalizar é feito um teste de erro para os valores passados por parâmetro para evitar que eles não sejam diferentes de string.

Aula 234 - classes abstratas

Classes abstratas são classes que não podem ser utilizadas diretamente, só podem ser extendidas.
O constructor() em uma classe sempre é executado. Mesmo quando não declaramos ele explicitamente quando instanciamos um objeto com o operador new ele será executado.
Quando criamos classes abstratas no JS devemos fazer um teste lógico que não permita que esta classe seja instanciada. Podemos retornar um erro quando ela for instanciada diretamente, assim ela só poderá ser iniciada como extends de uma outra sub-classe.
Os métodos também podem ser criados na classe principal e retornar uma mensagem de erro caso não sejam implementados na classe filha. Assim forçamos que o método seja criado sempre que uma classe extender a classe principal, sem que o método seja herdado dela.
Por fim, não devemos esquecer que as classes que extendem de outra classe devem possuir a função super() que deve receber os parâmetros que serão enviados para a classe pai.

Aula 235 - métodos estáticos

Métodos estáticos são métodos que não fazem parte das instâncias da classe mas fazem parte da função construtora.
Na prática significa que o método só é acessível pela classe e não pelo objeto instanciado a partir dela. Desse modo podemos executar o método no escopo da classe mas não no escopo do objeto criado.

Aula 236 - Desafio: Classe abstrata ContaBancaria

Na minha resolução criei a classe abstrata Conta e a classe Cliente. A classe Cliente extende a classe Conta. Criei uma implementação de números crescentes para o numero da conta.

Aula 238 - Resolução desafio: Classe abstrata ContaBancaria

A resolução foi identica a minha abordagem com a exceção que foi utilizado um throw Error para forçar que o método sacar() seja implementado na classe Cliente (para efeitos didáticos pois não faz sentido implementar a classe depositar() na classe abstrata e a sacar() na classe cliente).

Aula 239 - Desafio: Classes concretas ContaPoupanca e ContaCorrente

Na minha resolução quando fui consertar a lógica do saldo na conta corrente deixei um else if desnecessário. Nas classes ContaPoupanca e ContaCorrente passei como parametro aniversário e limite, cujos valores devem ser passados na instancia do objeto.

Aula 241 - Resolução desafio: Classes concretas ContaPoupanca e ContaCorrente

A resolução ficou bastante parecida com a minha. Um dos problemas é que passei parametros errados. Na verdade o cliente deve ser passado como parametro na instancia do objeto, e não os valores de limite e aniversário. O valor de aniversário é passado diretamente na classe e não como parametro. O valor do limite é atribuido como valor após a criação do objeto, assim o valor inicial para o atributo é '0'.
A lógica para os métodos ficoou igual, com a diferença que foi criada uma variável para verificar o saldo disponível, no meu caso fiz a soma do saldo com o limite diretamente no teste lógico.
Outra coisa é que aqui também utilizou-se um throw Error para informar a falta de saldo.

Aula 242 - Desafio: composição

Na minha resolução fiz tudo corretamente, passei como parametro para a conta o objeto Cliente criado, mas selecionei o atributo cliente.nome para o objeto Conta.

Aula 244 - Resolução desafio: composição

Quando passamos o objeto instanciado Cliente como parametro para o objeto Conta temos o objeto Cliente como atributo da super classe Conta. Dessa forma podemos acessar o objeto Cliente dentro do objeto ContaPoupanca ou ContaCorrente e podemos acessar os parametros do cliente dentro desses objetos.

Aula 245 - Cliente agora é classe abstrata

Minha resolução ficou bastante parecida. Não preciso criar a variável nome nas sub-classes PessoaFisica e PessoaJuridica pois elas já existem no classe Cliente. O mesmo ocorre com documento que eu deixei nas classes filhas.

Aula 247 / Aula 248 - Resolução desafio: classe abstrata Cliente / Refatoração: mostrar tipo documento

Na resolução as classes PessoaFisica e PessoaJuridica só passam os parametros e não contém nenhum atributo. Utilizei os construtores de cada um para definir o tipo de documento, o que acho que será feito na próxima aula. Aqui inicialmente o professor criou os atributos documento específicos para cada classe, mas mostrou que é melhor deixar tudo em um atributo único do tipo documento na classe Cliente.
Foi criado um método dadosCliente() para apresentar os dados do cliente (nome e número do documento).
Na refatoração para apresentar o tipo de documento foi criado um novo atributo na classe Cliente que recebe mais um parametro das classes PessoaFisica e PessoJuridica através do método super(). Cada uma dessas classes passa a string do tipo de documento no super(). Eu criei esse atributo na classe Cliente mas fiz um teste lógico no constructor de cada tipo de cliente para definir o tipo de documento.

Aula 249 - Polimorfismo

O polimorfismo ocorre quando temos uma classe abstrata que garante que as instancias das classes concretas tenham métodos comuns que garantem que os objetos sejam executados.
Para esse exemplo, criamos uma classe Transferir estática com o comando static. Essa classe possui um método que recebe como parametro objetos instanciados das classes concretas conta que tem os métodos sacar() e depositar() e o valor a ser tranferido.
Fazemos um teste para saber se os objetos passados como parametro estão vinculados a classe pai (no caso a classe abstrata Conta).
Em seguida executamos um try / catch para tentar executar o método. Caso o retorno dos métodos sacar() e depositar() retornem sem erros o try executa a operação. Caso haja algum erro o catch retorna um erro.

Seção 21 - Objetos - parte II

Aula 250 - getters e setters

Podemos criar métodos get e set para manipular alguns atributos dos objetos e proteger esses atributos de manipulação por outras formas.
Os métodos get devem retornar algo com o return (o valor que se espera do atributo) e o set recebe um parametro e o atribui ao atributo.
Utiliza-se essa abordagem para, por exemplo, fazer verificações antes de atribuir um valor, entretanto todo esse procedimento tem o problema de deixar as variáveis no escopo global.
Para resolver isso podemos criar uma função auto invocável(IIFE) para envolver o código do objeto protegendo as variáveis do escopo.
Para isso devemos atribuir ao objeto Window o objeto que queremos acessar de modo a passá-lo para fora da IFFE e acessá-lo pelo navegador. Esse método não funciona no NodeJS. Para funcionar no NodeJS substituimos o Window por this.

Aula 251 - Exercício proposto

Minha resolução para o exercício proposto.

Aula 252 - Resolução do exercício proposto

A resolução do professor foi igual a minha com algumas alterações quanto ao padrão das variáveis e o modo de apresentar o contador de consultas.

Aula 253 - Desafio getters e setters

Minha resolução ficou praticamente igual a do professor com exceção dos métodos de array.

Aula 254 - Resolução desafio

A resolução do desafio ficou igual, porém foi utilizado o método de array indexOf() para verificar se o dado inserido é repetido, na minha resolução utilizei o método some()(mais complicado).
Após a resolução analisamos quanto os dados ficaram protegidos ao não criarmos um método set para o array inteiro (só fizemos o set para adicionar usuários individualmente).
Caso tentemos alterar o array através de um apontamento para outra variável não conseguimos alterar o conteúdo do array, mesmo alterando o valor do apontador.
Entretanto utilizando métodos de array (pop(), push(), delete arr[i]) ainda podemos alterar o conteudo do array.

Aula 255 - Evite exportar referencias

Podemos proteger a variável utilizando uma propriedade Object.freeze(variável_protegida) que não irá permitir que utilizemos métodos de array em nossa variável.
Podemos também alterar a forma como retornamos a consulta do array em get usuarios criando uma cópia do array. Dessa forma estaremos retornando para o usuário sempre uma cópia do array mantendo ele protegido de alterações, mesmo com métodos de array.

Aula 256 - Introdução a descritores

Em um objeto comum podemos com um laço for...in percorrer os métodos e atributos contidos nele. Já alguns objetos como o objeto String isso não é possível. Para definir quais dessas propriedades estão disponíveis ou não em um objeto podemos manipular seus valores.
Para saber qual a configuração de propriedades de determinado objeto podemos utilizar o método Object.getOwnPropertyDescriptor(nome_do_objeto, atributo_a_consultar). As propriedades que podem ser verificadas são: configurable, enumerable e writable. Todas elas são true por padrão.
Podemos adicionar propriedades a um objeto com o método Object.defineProperty(nome_objeto, nome_propriedade { value: valor_propriedade }). Nesse caso as propriedades do objeto serão todas false e o valor do atributo não poderá ser modificado (em modo normal ele simplesmente não altera o valor, mas com 'use strict' ele retorna um erro).
No caso a propriedade writable desabilita a edição do valor, a propriedade enumerable impede que o atributo seja retornado no loop for...in por exemplo e o configurable impede que o atributo seja deletado, ou tenha suas propriedades alteradas (entre elas a própria propriedade configurable). A única propriedade que pode ser alterada é a writable de true para false e não para o inverso.
Podemos inserir várias propriedades com o método Object.defineProperties(nome_objeto, { propriedade1 { value: valor_propriedade1, propriedades: valor_propriedades }, propriedade2 { value: valor_propriedade2, propriedades: valor_propriedades }})

Aula 257 / Aula 259 - Object.create() / Object.assign() / Object.keys() values() e entries()

Quando criamos um objeto com o método Object.create() podemos passar os atributos diretamente, porém eles serão criados no protótipo do objeto. Entratanto podemos criar um objeto com o create() passando os parametro de um objeto superior que vai passar por herança seus métodos. Fazemos isso com a seguinte expressão: Object.create(objeto_pai, { atributo: { value: valor }}).
O método Object.assign(target, origin) atribue os valor de um objeto com o enumerable: true para o objeto target. O objeto target passa a retornar o valor do assign. Podemos passar novos valores em seguida.
Note que quando atribuimos um Object.assign({}, origin) não alteramos o conteudo dos objeto, apenas incluimos os dados no objeto {}.
Podemos passar mais de um objeto como origem dos dados. Nesse caso se tivermos atributos com o mesmo nome, o último vai sobreescrever.
Se utilizarmos o spread operator(...) nos dois objetos podemos obter o mesmo resultado do assign() sem alterar o objeto target.
Em todos os casos (com assign() ou com spread operator ), caso criemos um atributo não enumerável (com Object.defineProperty()) esse atributo não será incluido no resultado.
O método Object.keys(nome_do_objeto) mostra todos os atributos enumeráveis de um objeto.
O método Object.value(nome_do_objeto) mostra os valores armazenados nos atributos enumeráveis.
E o método Object.entries(nome_do_objeto) mostra um array de arrays (array bidimensional) com os pares chave / valor.

Aula 260 - Destructuring

Podemos aplicar o destructuring em objetos e criar variáveis a partir dos seus valores. Podemos inclusive alterar o nome das variáveis, lembrando que o nome dos atributos do objeto são o nome das variáveis por padrão.

Aula 261 - Congelar propriedades de um objeto

Quando utilizamos o método freeze() em um objeto não conseguimos criar, alterar ou remover suas propriedades. Se executarmos o comando Object.getOwnPropertyDescriptors() veremos que as propriedades writable e configurable são false. É possível enumerar esse objeto.
Quando utilizamos o método seal() em um objeto não conseguimos criar ou remover suas propriedades mas podemos alterar. Se executarmos o comando Object.getOwnPropertyDescriptors() veremos que a propriedade configurable é false. É possível enumerar esse objeto.

Aula 262 - Deep freeze

Quando utilizamos os métodos de proteção estudados acima temos uma proteção superficial. Isso porque ainda podemos manipular objetos ou arrays internos ao objeto protegido.
Para contornar esse problema podemos aplicar o Object.freeze() para cada objeto interno do objeto, mas isso não é muito programático.
Dees modo criamos uma função que analisa cada item do objeto e aplica o método Object.freeze(). Nessa função caso seja identificado um objeto interno ao objeto principal a função é reexecutada para aplicar o Object.freeze() nesse objeto interno também. Assim temos um loop que aplica o método em todas as camadas internas do objeto principal protegendo todas. Devemos tabém testar se a propriedade é diferente de null pois o typeof de null retorna um Object

Aula 263 - diferença entre keys() e getOwnPropertyNames()

Se ainda ficou na dúvida sobre o motivo de termos usado Object.getOwnPropertyNames() e não Object.keys() em nossa função deepFreeze, peço que rode o código abaixo e analise o resultado mostrado no console:
A diferença é que o método Object.keys() não processa os atributos em que a propriedade enumerate esteja setada como false

Aula 264 / Aula 265 - isFrozen / Outros métodos de checagem

Podemos fazer uma verificação se o objeto está protegido com o método Object.isFrozen(). Esse método retorna um booleano, entretanto mesmo que passemos atributos writable e configurable como false, o objeto continua sendo considerado não congelado. Para torná-lo freeze precisamos passar o método Object.preventExtensions() para torná-lo congelado.
Da mesma forma que temos o método Object.isFrozen() temos os métodos Object.isSealed() e Object.isExtensible()

Aula 266 - Symbols

Symbol é um tipo primitivo do ECMAScript5. Symbol é um tipo de dado que tem uma chave única. Ele não pode ser instanciado com o operador new pois ele não é um constructor.
Nas versões anteriores do JS quando criávamos um objeto precisávamos declarar o nome da propriedade como uma string. Na versão atual podemos passar entre '[ ]' códigos JS que serão interpretados para criar o nome do atributo (p.ex: { ['teste' + (++n)]: 'valor' } ). O nome desse procedimento é Computed Property Name.
O Symbol é uma forma de criar propriedades que ficam relativamente protegidas pois não podem ser acessadas do modo normal mas sim com o seu valor entre [] (p.ex: objeto[nome_symbol]). Esse primitivo é util para evitar que se use propriedades comuns de modo equivocado.

Aula 267 - Symbol como propriedade de objeto

Aqui criamos uma propriedade contador que pode ser facilmente acessada e alterada apenas acessando seu nome de atributo.
Podemos criar essa mesma propriedade através de uma variável do tipo Symbol(). Essa variável continua visível, entretanto é mais difícil de manipular que as propriedades declaradas diretamente.

Aula 268 - Manter o Symbol isolado

Para protegermos o Symbol() podemos encapsulá-lo em uma IFFE que irá retornar a classe. Desse modo não teremos acesso a propriedade criada com Symbol fora do escopo da IFFE.
Com isso não sabemos qual o nome da variável criada mesmo quando utilizamos o método Object.getOwnPropertySymbols(). Recebemos como retorno apenas que a variável é um [ Symbol() ].
Todo esse procedimento não impossibilita que se mainpuole a variável, ou seja, não é uma proteção absoluta da variável, porém impede que ela seja manipulada sem que se tenha certeza de que esse é o objetivo.

Aula 269 - Map e WeakMap

Map e WeakMap são estruturas de dados que assim como os objetos recebem pares de dados chave / valor. A diferença é que em um objeto tradicional o nome da chave é uma string.
A primeira diferença é que como podemos passar qualquer valor para o Map() para incluir os conjuntos chave / valor precisamos utilizar o método set e para recuperar os dados do atributo precisamos utilizar o método get.
Para criar o conjunto de dados do Map() devemos passar os dados com a seguinte estrutura:
const nome_da_variável = new Map([ [chave1, valor1], [chave2, valor2], ...[chaveN, valorN] ])
Lembrando que os valores de chave e valor podem ser quaisquer tipos de dados válidos.
Temos diversos métodos para utilizar com o Map(), entre eles:
get(), has(), keys(), values(), entries(), size.
Podemos iterar sobre os valores do Map() utilizando os métodos keys() ou values() em conjunto com o for...of.
Já o WeakMap é mais fraco, mas mais eficiente em performance, pois trabalha de forma diferente na liberação da memória, pois uma vez que não tenhamos mais acesso as variáveis do WeakMap() elas são removidas pelo garbage collector. Outra diferença é que no weakMap() não temos como fazer iterações.

Aula 270 - Como o Babel lida com propriedades privadas

Já existe especificação no JS para criar propriedades privadas nativamente (em 10/2020) mas essa opção ainda não é suportada pelos navegadores.
Podemos aplicar essas novas abordagens utilizando o Babel que é um transpilador de novas implementações do JS.
Podemos fazer alguns testes em Babel > Try it out. Se testarmos o seguinte código:
class MyClass {
publico = 0
}
ou: class MyClass {
constructor() {
this.publico = 0
}
}
veremos o código adaptado para outras versões de navegadores (de acordo com a seleção de versão).
Se testarmos o código abaixo (que cria uma variável privada):
class MyClass {
#privado = 0
}
teremos a seguinte construção pelo Babel:
"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var _privado = /*#__PURE__*/new WeakMap();

var MyClass = function MyClass() {
_classCallCheck(this, MyClass);

_privado.set(this, {
writable: true,
value: 0
});
};
Note que o Babel cria um WeakMap() para tornar a chave privada.

Aula 272 - Set e Weakset

Aula 249 - Polimorfismo

Enquanto o Map e o WeakMap tratam elementos que se assemelham a objetos com pares chave e valor, o Set e WeakSet tratam elementos que se assemelham a arrays.
Com o Set incluimos no objeto apenas um valor que deve ser único, ou seja, valores repetidos não são considerados. Para adicionar valores no objeto utilizamos o método add() e podemos remover com o método delete(valor_a_remover) (não podemos remover pelo índice pois ele não é reconhecido).
Temos o método has(valor_procurado) que retorna um booleano caso o valor exista ou não. Podemos também iterar os valores com for...of.
O WeakSet() tem características semelhantes ao WeakMap(). Ele não aceita valores primitivos, apenas objetos.
O WeakSet() também não é iterável.