Front End

25 mar, 2016

O que todo desenvolvedor JavaScript precisa saber

Publicidade

Nos últimos anos, o JavaScript vem passando por uma grande revolução: uma enorme adoção por partes das empresas, polêmicas e algumas rivalidades:

  • ES6/ES7 x TypeScript
  • Angular x React
  • Gulp x Grunt
  • Functional Programming x OOP

Apesar disso, existe uma parte fundamental da linguagem que todos deveríamos saber:

1. Call x Apply

As funções call e apply nos deixam invocar métodos como se estivéssemos no contexto de um outro objeto:

var myself = { firstName: 'Matheus', lastName: 'Lima' };

function showFullName() {
    console.log(this.firstName + " " + this.lastName);
}

showFullName.call(myself);
// Matheus Lima

showFullName.apply(myself);

Qual a importância?

A importância desses dois métodos e o porquê de eles serem tão usados (principalmente em libs) é bem simples: apply e call nos permitem pegar métodos emprestados, reduzindo, assim, a quantidade total de código gerada e seguindo o DRY.

2. Closures

A combinação de uma função e a referência ao seu estado externo é uma closure. Uma aplicação comum de closures são os IIFEs:

(function() {
    var a = 1;
})();
 
var b = 2;

A variável a é privada por ficar em uma closure. Já a b é uma variável global.

Outra abordagem para a criação de uma closure é basicamente criar uma função dentro de outra, dessa forma:

function init() {
    var name = 'Matheus';
    function hello() {
        console.log('Olá: ' + name);
    }
    hello();
}
 
init();
// Olá: Matheus
 
hello();
// Uncaught ReferenceError: hello is not defined(…)

A função de dentro (hello) tem acesso ao escopo externo dela (init). Porém, ao sairmos de init, perdemos a visibilidade da função hello.

Qual a importância?

A capacidade de esconder informações também é conhecida como data privacy. Isso é essencial para que possamos esconder informações que deveriam ser privadas e programar para uma interface e não para uma implementação.

3. This

A palavra-chave this no JavaScript funciona de uma maneira um pouco diferente das outras linguagens. Em linguagens OO comuns, o this se refere à instância da classe corrente. Porém, no JavaScript, o valor de this é determinado pelo contexto de invocação da função e onde elas foram chamadas.

Escopo Global

No escopo global (em um browser), o this se refere ao objeto window, tanto dentro quanto fora de uma função:

document.write(this);  //[object Window]
 
function func() {
    return this;
}
document.write(func());  //[object Window]

Método de objeto

Quando usado dentro de um método de um objeto, o this se refere ao próprio objeto:

var object = {
    func: function() {
        return this;
    }
};
document.write(object.func());  //[object Object]

E no caso de objetos aninhados, o this vai se referir ao objeto pai mais próximo:

var father = {
    name: "father",
    child: {
        name: "child",
        func: function() {
            return this.name;
        }
    }            
}
 
document.write(father.child.func()); 
//child

É bem comum esquecermos a regra acima, principalmente no uso de loops:

'use strict';
 
var object = {
    name: 'Matheus',
    friends: ['João', 'Ana' ],
    loop: function() {
        this.friends.forEach(function(friend) {
            console.log(this.name + ' knows ' + friend);
        });
    }
};
 
object.loop();
// TypeError: Cannot read property 'name' of undefined

Uma forma fácil de corrigir esse problema é simplesmente guardar o valor do this em uma variável e, ao invés de chamar o this, chamar essa variável:

var self = this;
loop: function() {
    this.friends.forEach(function(friend) {
        console.log(self.name + ' knows ' + friend);
    });
}

Função sem contexto

Quando usamos o this numa função invocada sem contexto, o bind é feito no contexto global, mesmo se a função for definida dentro de um objeto:

var context = "global";
 
var object = {  
    context: "object",
    method: function () {                  
        function func() {
            var context = "function";
            return this.context; 
        };
        return func(); // INVOCADO SEM CONTEXTO
    }
};
 
document.write(object.method()); //global

Função na Prototype Chain

Quando um método está na prototype chain de um objeto, o this desse método vai se referir ao objeto que o está chamando, mesmo se o método não estiver definido nesse objeto:

var object = {
    func: function () {
        return this.name;
    }
};
 
var anotherObject = Object.create(object);
anotherObject.name = 'Matheus';
 
document.write(anotherObject.func()); //Matheus

Qual a importância?

O this é um dos maiores responsáveis por erros para quem está iniciando com o JavaScript. Portanto, entender como ele funciona é de extrema importância para quem quer dominar a linguagem e gerar menos bugs.

4. Bind

Agora que já entendemos o this em JavaScript e os métodos call e apply, podemos estudar o método bind.

O bind é muito semelhante ao call e apply: serve para passarmos um contexto para uma função, que não é dela, e podermos executá-la. A diferença é que call e apply invocam a função imediatamente:

var person = {  
  name: "Matheus",
  hello: function(thing) {
    console.log(this.name + " disse Olá " + thing);
  }
}
 
person.hello.call(person, "Mundo"); // Matheus disse Olá Mundo

Enquanto bind retorna uma nova função, que quando for executada terá o contexto que passamos.

var person = {  
  name: "Matheus",
  hello: function(thing) {
    console.log(this.name + " disse Olá " + thing);
  }
}
 
var hello = person.hello.bind(person);
hello("Mundo"); // Matheus disse Olá Mundo

É comum usarmos o bind para o disparo de eventos:

/* code */
 
element.addEventListener('click', this.onClick.bind(this));

E também no mundo React:

render: function() {
    return (
      <div>
        {this.props.items.map(function(item, i) {
          return (
            <div onClick={this.handleClick.bind(this, i)} key={i}>{item}</div>
          );
        }, this)}
      </div>
    );
}

Qual a importância?

Basicamente a mesma de apply e call.

5. Map, Filter e Reduce

Você provavelmente já passou por alguma situação em que era necessário, por exemplo, iterar sobre um array, ou então verificar se existe alguma propriedade nele ou mesmo simplesmente gerar um novo array com base no primeiro. Os métodos map, filter e reduce nos ajudam nessas situações, além de nos fazer pensar mais em termos de programação funcional.

Map:

Dado um array qualquer, como podemos fazer para transformá-lo ou mapeá-lo em um outro array?
Existe a forma difícil (sem map):

var months = [
    {shortName: 'JAN', fullName: 'January',  number: 1},
    {shortName: 'FEB', fullName: 'February', number: 2},
    ...
];
 
var shortNameMonths = [];
for (var i = 0; i < months.length; i++) {
    shortNameMonths.push(months[i].shortName);
}
 
console.log(shortNameMonths); // ['JAN', 'FEB', ...]

E existe a forma fácil (com map):

var months = [
    {shortName: 'JAN', fullName: 'January',  number: 1},
    {shortName: 'FEB', fullName: 'February', number: 2},
    ...
];
 
var shortNameMonths = months.map(function(month) {
    return month.shortName;
});
 
console.log(shortNameMonths); // ['JAN', 'FEB', ...]

Filter:

Dado o mesmo array, como podemos gerar um novo filtrando apenas os 6 primeiros meses do ano?

Com o filter é bem fácil fazer isso:

var months = [
    {shortName: 'JAN', fullName: 'January',  number: 1},
    {shortName: 'FEB', fullName: 'February', number: 2},
    ...
];
 
var firstSemester = months.filter(function(month) {
    return month.number <= 6;
});
 
console.log(firstSemester); // ['JAN', 'FEB', .. , 'JUN']

Reduce:

Dado o array dos meses que temos trabalhado até então, como podemos fazer para gerar uma acumulação de valores ou reduzi-lo em algum valor específico?

Com o reduce seria mais ou menos assim:

var months = [
    {shortName: 'JAN', fullName: 'January',  number: 1},
    {shortName: 'FEB', fullName: 'February', number: 2},
    ...
];
 
var monthsAcc = months.reduce(function(acc, month) {
    return acc + '/' + month.shortName;
}, '');
 
console.log(monthsAcc); // /JAN/FEB/MAR/APR...

Pra quem conhece o Redux (um state container muito comum em aplicações que usam o React), uma curiosidade é que a ideia dele está basicamente resumida na ideia de uma função reduce: a Reducer Function.

Qual a importância?

Os métodos map, filter e reduce são a base da programação funcional não só em JavaScript, mas em diversas outras linguagens. E sem saber programação funcional, não há como tirar 100% de proveito de JavaScript e do seu ecossistema.

6. Prototype

A herança em JavaScript é feita por meio dos prototypes. Ela funciona de uma maneira um pouco diferente da herança clássica, o que pode gerar um pouco de confusão mesmo para os desenvolvedores mais experientes em JavaScript.

Podemos pensar em objetos em JavaScript como agregadores de propriedades dinâmicas. Quando um objeto tenta acessar qualquer propriedade sua e não encontra, ele vai procurar no seu prototype. E se não estiver lá, no prototype de seu prototype até que a propriedade seja encontrada, ou então, essa corrente, chamada de Prototype Chain, se acabe:

var Animal = {
    walk: function() {
        console.log("I'm walking...");
    }
};
 
var Dog = Object.create(Animal);
 
Animal.walk(); // I'm walking...
Dog.walk(); // I'm walking... 
 
var Airplane = {
    fly: function() {
        console.log("I'm flying...");
    }
};
 
Airplane.walk(); // Uncaught TypeError: Airplane.walk is not a function(…)

Animal consegue invocar o método walk porque o mesmo pertence a ele. Dog também consegue invocar porque apesar do mesmo não pertencer a ele, pertence a seu prototype. No caso de Airplane, porém, nem ele e nenhum prototype seu encontrou o método walk até o fim da Prototype Chain.

Qual a importância?

Um dos conceitos mais usados em qualquer linguagem de programação é a Herança; e com JavaScript ela é feita com o Prototype. Entender bem não apenas o Prototype como também o Prototype Chain é fundamental para dominar o JavaScript.

7. Hoisting

A tradução de hoist é içar, levantar ou suspender. E é isso que acontece em JavaScript quando declaramos uma variável: ela é levantada até o topo do escopo.

Qual o retorno da função abaixo?

var n = 1;
function init(){
    console.log(n);
    var n = 2;
}
init();

As respostas mais comuns são 1 ou 2, mas a resposta correta é undefined. A declaração de variáveis em JavaScript é hoisted (ou elevada), mas não sua inicialização. Portanto, o código acima é equivalente a esse:

var n;
function init() {
    var n;
    console.log(n);
    n = 2;
}
n = 1;
init();

Para evitar problemas inesperados, tente sempre declarar todas as variáveis no topo do escopo, mesmo que você não as tenha inicializado ainda.

Ou então atualize para o ES6 e passe a usar as keywords let e const. Elas funcionam da maneira esperada:

// com var, o valor do segundo console.log não é esperado
function var() {
  var n = 1;
  if (true) {
    var n = 2;
    console.log(n); // 2
  }
  console.log(n); // 2 
}
 
// com let, o valor do segundo console.log é esperado
function let() {
  let n = 1;
  if (true) {
    let n = 2;
    console.log(n); // 2
  }
  console.log(n);  // 1
}

Qual a importância?

Evitar futuros bugs ao entender uma das partes mais problemáticas da linguagem.

E é isso! Ficou alguma dúvida, tem alguma observação ou acha que faltou algum ponto? Deixe aqui embaixo nos comentários!

Até a próxima.

***

Esse post foi originalmente publicado no Medium pessoal do autor. Confira aqui.