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:
- Diretamente
- Como um método
- Explicitamente aplicada
- 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/



