Front End

11 out, 2016

Entendendo Hoisting e escopo: o que mudou no ES2015 – Parte 01

Publicidade

Alguns programadores que começam em Javascript sempre acabam passando por problemas para entender Hoisting e Escopo. Afinal, WTF, man?

Vejamos o código abaixo:

var name = "Marquinho";
(function () {
    console.log(typeof family); function
    console.log(typeof neighborhood); // undefined
    
    family();
    neighborhood();
    
    function family() {
        console.log(name); // Marquinho
        
        function brother() { 
            var name = "Marcus Vinicius";
            console.log(name);
            return name;
        }
 
        function mother() {
            console.log(name); // undefined
            if (!name) {
              var name = "Nair Madalena";
              console.log(name); // Nair Madalena 
            }
            return name;
        }
 
        function brother(name) {
            console.log(name); // Marquinho
            var pharse = "O apelido dele é: ";
            console.log(pharse + name + ', o nome da mãe é ' + mother()); // O apelido dele é: Marquinho, o nome da mãe é: Nair Madalena
        }
        brother(name);
    }
    
    var neighborhood = function() {
        var placeNear = "Supermercado Sonda";
        console.log("Em minha vizinhança perto tem um " + placeNear);
    }
 
})();

Nele, declaramos name como uma variável global com o valor “Marquinho” e temos a chamada da função family() e neighborhood(). A diferença é que a função familytem como escopo IIFE e funciona mesmo sendo definida depois. Já neighborhood não é hoisted, apenas sua declaração.

Em JavaScript, todas as declarações de funções e variáveis são movidas para o topo de cada escopo, e isto chamamos de hoisting. Em tempo de execução, ao declarar sua variável e função, a mesma é sempre elevada ao topo.

Porém, somente sua declaração é hoisted; a sua inicialização não. Funções anônimas ou expressão de função também são hoisted e acontece a mesma coisa, por isso a função neighborhood, ao ser invocada na linha 4, nos retorna undefined.

Vamos entender como nosso código é interpretado:

var name;
    name = "Marquinho";
 
(function () {
    console.log(typeof family); // function
    console.log(typeof neighborhood); // undefined
 
    family();
    neighborhood();
    
    function family() {
        console.log(name); // Marquinho
        
        function brother() { 
            var name;
            name = "Marcus Vinicius";
            console.log(name);
            return name;
        }
 
        function mother() {
            console.log(name); // undefined
            var name; // undefined
            if (!name) {
              name = "Nair Madalena";
              console.log(name); // Nair Madalena 
            }
            return name;
        }
 
        function brother(name) {
            console.log(name); // Marquinho
            var pharse
            pharse = "O apelido dele é: ";
            console.log(pharse + name + ', o nome da mãe é ' + mother()); // O apelido dele é: Marquinho, o nome da mãe é: Nair Madalena
        }
        brother(name);
    }
    
    var neighborhood
    neighborhood = function() {
        var placeNear;
        placeNear = "Supermercado Sonda";
        console.log("Em minha vizinhança perto tem um " + placeNear);
    }
 
})();

Declaração e inicialização?

É muito importante entender a diferença entre declarar e inicializar uma variável em JavaScript.

Declaração:

// declara variável 'name' 
var name;

Inicialização:

// inicializado com o valor 'Celso' 
name = 'Celso' ;

No ES5, temos dois escopos para as variáveis: escopo de função e global.

O que é Escopo?

Em ES5, as funções são nosso delimitador de escopo de fato para declaração de variáveis. Isso significa que blocos usuais de loops e estruturas condicionais (como if,for, while, switch e try) NÃO delimitam escopos, diferente de muitas outras linguagens como C, C++, Java e ES2015.

Portanto, esses blocos compartilham do mesmo escopo que a função que os contém. Dessa forma, pode ser perigoso declarar variáveis dentro de blocos, já que vai parecer que a variável pertence apenas ao bloco. Esse é um problema comum para programadores vindos de linguagens com escopos em blocos.

Como entrar em um escopo?

Temos quatro maneiras:

  • Definido pela linguagem: todo escopo possui o this e, caso seja uma função, também o arguments;
  • Declaração de uma variável: variáveis declaradas como var name;
  • Declaração de uma função: funções declaradas na forma function family() {};
  • Parâmetros de uma função: caso uma função seja chamada na forma brother (name), podendo ser brother (name, mother, …), sendo name e mother entram no escopo da função.

Como poderia ficar nosso código:

var val = 1;
scopeBloc();
 
function scopeBloc() {    
    for(var i = 0; i < 10; i++) {
        var newVal = val + i;
        console.log(newVal); // 10
     }     
     console.log(newVal); // 10
     console.log(val); // 1 
}

No código acima ainda podemos acessar a varivel newVal fora do bloco do loop, bem como podemos criar um escopo dentro do loop em ES5 utilizando ‘IIFE’ (sobre esse assunto, recomento a leitura deste post, feito por meu amigo Pedro Araujo). Essa era nossa única maneira de se criar um escopo dentro de um bloco, mas com a vinda do ES2015 isto mudou.

var val = 1;
scopeBloc();
 
function scopeBloc() {    
    for(var i = 0; i < 10; i++) {
        (function() {
            var newVal = val + i;
            console.log(newVal); // 10
        }());
    }     
    console.log(newVal); // ReferenceError: newVal is not defined
    console.log(val); // 1 
}

Vamos ver se ficou claro:

var val = 1; // escopo global
var text = "Estudando Escopo"; // escopo global
 
function scopeBloc() {    
    for(var i = 0; i < 10; i++) { // i escopo local
        (function() {
            var newVal = val + i; // newVal escopo do bloco if
            console.log(newVal); // vai fazer o loop de 1 a 10
            console.log(text); 
            // var fazer o loop com a string Estudando Escopo
        }());
     }
 
     var getPhrase =  function() {
         var val = 2; // ecopo de bloco
         Pharse = "I love JavaScript"; 
         // escopo global (var não foi utilizada)
 
        console.log(val); // 2
        console.log(i); // 10
        console.log(text); // Estudando Escopo
        console.log(Pharse); // I love JavaScript
     }();
 
     console.log(newVal); // ReferenceError: newVal is not defined
     console.log(val); // 1 
}
 
scopeBloc();
 
console.log(val); // 1
console.log(text); // Estudando Escopo
console.log(Pharse); // I love JavaScript

E o que é this?

Aqui está uma das partes mais confusas do JavaScript. Como o this funciona? O que come? Onde vive?

Brincadeiras à parte, this é uma referência ao contexto no qual a função está sendo executada, ao objeto ao qual ela pertence. Em grande parte de outras linguagens othis é uma referência ao objeto atual instanciado. Contudo, para dar um nó na sua mente, em JavaScript o this vai depender de como sua função foi inicializada.

Algumas formas de chamar uma função:

  1. Diretamente
  2. Como um método
  3. Explicitamente aplicada
  4. Como construtor

Diretamente:

Quando uma função é chamada diretamente, this fará referência ao contexto global, que no caso dos navegadores é o objeto window.

var text = 'Escopo em Javascript'
function createPhrase(textA, textB) {
    var textC = 'Quando uma função é chamada diretamente'
    console.log(this); // imprimi window
    console.log(textA + textB + ' - ' + textC); 
    // Entendendo como funciona Escopo em Javascript - Quando uma função é chamada diretamente 
}
 
createPhrase('Entendendo como funciona ', text);

Como um método:

São funções armazenadas dentro das propriedades de um objeto. Se uma função for chamada como um método de um objeto, então this, dentro dessa função, fará referência a esse objeto.

var text = 'Escopo em Javascript'
var myObject = {
    textA: 'Entendendo como funciona ',
    textB: 'Quando uma função é chamada com um método',
    createPhrase: function (textC) {
        console.log(this.textA + textC + ' - ' + this.textB); 
        // Entendendo como funciona Escopo em Javascript - Quando uma função é chamada com um método
    }
};
 
myObject.createPhrase(text);

Explicitamente aplicada:

Uma função pode ser aplicada explicitamente a qualquer objeto, e para isso utilizamos os métodos call ou apply. Saiba mais neste artigo aqui.

Os métodos call e apply nos permitem controlar o valor de this e assim definir a qual objeto ele fará referência. A diferença entre eles é a forma que recebem os parâmetros e também a velocidade de execução que pode variar de navegador para navegador.

var text = 'Escopo em Javascript';
var myObject = {
    phraseC: 'Entendendo como funciona ',
    phraseD: 'Quando uma função é aplicada explicitamente'
};
 
function createPhrase(phraseA, phraseB) {
    console.log(this); // imprimi objeto
    console.log(this.phraseC + phraseA  + ' - ' +  this.phraseD  + ' => ' + phraseB); 
    // Entendendo como funciona Escopo em Javascript - Quando uma função é aplicada explicitamente => entenderam ?
}
 
createPhrase.call(myObject, text, 'entenderam ?');  
createPhrase.apply(myObject, [text, 'entenderam ?']);

Na função createPhrase faço referência ao objeto ‘myObject’. Com isso, consigo acessar os nós dele: this.phraseC que tem como valor Entendendo como funciona e this.phraseD que tem como valor Quando uma função é aplicada explicitamente.

Como um construtor:

Quando uma função é usada como um construtor (usando a palavra-chave new), thisfará referência ao novo objeto que está sendo construído:

function work() {
    this.startWork = '8:00';
    this.finishWork = '19:00';
    this.officialName = officialName();
 
    this.getstartWork = function() {
        return this.startWork;
    };
 
    this.getfinishWork = function() {
        return this.finishWork;
    };
 
    this.getOfficialName = function() {
     return this.officialName;
    };
};
 
function officialName() {
    var name = 'Luiz Celso F Alves';
    return name;
};
 
var myWork1 = new work();
var myWork2 = new work();
 
console.log('Entrada: '+myWork1.getstartWork());// Entrada: 8:00
console.log('Saída: '+myWork1.getfinishWork()); // Saída: 19:00
console.log('Nome: '+myWork1.getOfficialName());// Nome: Luiz Celso F Alves
 
myWork2.startWork = '10:00';
console.log('Novo horario de entrada: '+myWork2.startWork);// Novo horario de entrada: 10:00

E por hoje é só!

Ficou alguma dúvida, tem alguma observação a fazer ou algum comentário? Fique à vontade nos campos abaixo. Até a próxima!

***

Artigo publicado originalmente em: http://www.concretesolutions.com.br/2016/09/12/hoisting-e-escopo-es2015/