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.
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.
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.
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.
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.
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().
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').
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().
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.
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.
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.
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'.
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:desafioAlterar 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...
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 regressivaPara 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árioExecutei 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.
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: desafioResoluçã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.
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.
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.
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.
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.
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.
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.
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
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çãoNesta 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.
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.
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.
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().
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.
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 patternO 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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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 ContaBancariaA 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 ContaCorrenteNa 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.
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çãoQuando 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 abstrataMinha 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.
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.
Minha resolução para o exercício proposto.
Aula 252 - Resolução do exercício propostoA 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 settersMinha 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.
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.
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 }})
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.
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.
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
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
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()
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.
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.
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.
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.
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.
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.