Qual é o escopo das variáveis ​​em JavaScript?

2013

Qual é o escopo das variáveis ​​em javascript? Eles têm o mesmo escopo dentro e não fora de uma função? Ou isso importa? Além disso, onde estão as variáveis ​​armazenadas se definidas globalmente?

lYriCAlsSH
fonte
4
Aqui está outro link interessante para ter em mente esse problema: " Explicando o escopo e os fechamentos do JavaScript ".
Full-Stack Software Engineer
9
Aqui está um artigo que explica muito bem. Tudo o que você precisa saber sobre o escopo de variáveis Javascript
Saurab Parakh
2
O e-book de Kyle Simpson mencionado anteriormente está disponível para leitura no Github e fornece tudo o que você precisa saber sobre escopos e fechamentos de JavaScript. Você pode encontrá-lo aqui: github.com/getify/You-Dont-Know-JS/blob/master/… Faz parte da série de livros "Você não conhece JS" , o que é ótimo para todos que gostariam de saber mais sobre JavaScript.
3rik82

Respostas:

2536

TLDR

O JavaScript possui escopo e fechamentos lexicais (também chamados estáticos). Isso significa que você pode dizer o escopo de um identificador olhando o código-fonte.

Os quatro escopos são:

  1. Global - visível por tudo
  2. Função - visível dentro de uma função (e suas subfunções e blocos)
  3. Bloco - visível dentro de um bloco (e seus sub-blocos)
  4. Módulo - visível dentro de um módulo

Fora dos casos especiais do escopo global e do módulo, as variáveis ​​são declaradas usando var(escopo da função), let(escopo do bloco) e const(escopo do bloco). A maioria das outras formas de declaração de identificador tem escopo de bloco no modo estrito.

Visão geral

Escopo é a região da base de código sobre a qual um identificador é válido.

Um ambiente lexical é um mapeamento entre nomes de identificadores e os valores associados a eles.

O escopo é formado por um aninhamento vinculado de ambientes lexicais, com cada nível no aninhamento correspondendo a um ambiente lexical de um contexto de execução ancestral.

Esses ambientes lexicais vinculados formam um escopo "cadeia". A resolução do identificador é o processo de busca ao longo dessa cadeia por um identificador correspondente.

A resolução do identificador ocorre apenas em uma direção: para o exterior. Dessa maneira, os ambientes lexicais externos não podem "ver" os ambientes lexicais internos.

Existem três fatores pertinentes para decidir o escopo de um identificador em JavaScript:

  1. Como um identificador foi declarado
  2. Onde um identificador foi declarado
  3. Se você está no modo estrito ou não estrito

Algumas das maneiras pelas quais os identificadores podem ser declarados:

  1. var, leteconst
  2. Parâmetros da função
  3. Parâmetro do bloco de captura
  4. Declarações de função
  5. Expressões de funções nomeadas
  6. Propriedades implicitamente definidas no objeto global (isto é, falta varno modo não estrito)
  7. import afirmações
  8. eval

Alguns dos identificadores de locais podem ser declarados:

  1. Contexto global
  2. Corpo da função
  3. Bloco ordinário
  4. O topo de uma estrutura de controle (por exemplo, loop, if, while etc)
  5. Corpo da estrutura de controle
  6. Módulos

Estilos de declaração

var

Identificadores declarados usando var têm escopo de função , além de quando são declarados diretamente no contexto global; nesse caso, eles são adicionados como propriedades no objeto global e têm escopo global. Existem regras separadas para seu uso em evalfunções.

deixe e const

Identificadores declarados usando lete const têm escopo de bloco , exceto quando são declarados diretamente no contexto global; nesse caso, eles têm escopo global.

Nota: let, conste var estão todos içada . Isso significa que sua posição lógica de definição é a parte superior de seu escopo (bloco ou função). No entanto, as variáveis ​​declaradas estão sendo usadas lete constnão podem ser lidas ou atribuídas até que o controle tenha passado o ponto de declaração no código-fonte. O período intermediário é conhecido como zona morta temporal.

function f() {
    function g() {
        console.log(x)
    }
    let x = 1
    g()
}
f() // 1 because x is hoisted even though declared with `let`!

Nomes de parâmetros de função

Os nomes de parâmetros da função têm escopo definido para o corpo da função. Observe que há uma leve complexidade nisso. As funções declaradas como argumentos padrão fecham-se sobre a lista de parâmetros , e não o corpo da função.

Declarações de função

As declarações de função têm escopo de bloco no modo estrito e escopo de função no modo não estrito. Nota: o modo não estrito é um conjunto complicado de regras emergentes com base nas implementações históricas peculiares de diferentes navegadores.

Expressões de funções nomeadas

As expressões de funções nomeadas têm um escopo definido para elas mesmas (por exemplo, para fins de recursão).

Propriedades definidas implicitamente no objeto global

No modo não estrito, as propriedades implicitamente definidas no objeto global têm escopo global, porque o objeto global fica na parte superior da cadeia de escopo. No modo estrito, isso não é permitido.

avaliação

Nas evalcadeias, as variáveis ​​declaradas usando varserão colocadas no escopo atual ou, se evalusadas indiretamente, como propriedades no objeto global.

Exemplos

A seguir irá lançar uma ReferenceError porque os nomes x, ye znão têm nenhum significado fora da função f.

function f() {
    var x = 1
    let y = 1
    const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)

A seguir, será gerado um ReferenceError para ye z, mas não para x, porque a visibilidade de xnão é restringida pelo bloco. Blocos que definem os corpos de estruturas de controle como if, fore while, se comportam de forma semelhante.

{
    var x = 1
    let y = 1
    const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope

A seguir, xé visível fora do loop porque varpossui o escopo da função:

for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)

... por causa desse comportamento, você precisa ter cuidado ao fechar as variáveis ​​declaradas usando varloops. Há apenas uma instância da variável xdeclarada aqui e ela fica logicamente fora do loop.

As seguintes impressões são impressas 5cinco vezes e depois são impressas 5pela sexta vez pela parte console.logexterna do loop:

for(var x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop

A seguinte impressão é impressa undefinedporque xtem escopo de bloco. Os retornos de chamada são executados um por um assincronamente. Novo comportamento de letmeios variáveis que cada função anônima fechada sobre uma variável diferente chamada x(ao contrário do que teria feito com var), e assim inteiros 0através 4são impressos .:

for(let x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined

O seguinte NÃO lançará a ReferenceErrorporque a visibilidade de xnão é restringida pelo bloco; no entanto, ela será impressa undefinedporque a variável não foi inicializada (devido à ifinstrução).

if(false) {
    var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised

Uma variável declarada na parte superior de um forloop usando lettem o escopo definido para o corpo do loop:

for(let x = 0; x < 10; ++x) {} 
console.log(typeof x) // undefined, because `x` is block-scoped

A seguir, será gerado um ReferenceErrorporque a visibilidade de xé restringida pelo bloco:

if(false) {
    let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped

Variáveis ​​declaradas usando var, letou consttodos com escopo definido para módulos:

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

A seguir, declararemos uma propriedade no objeto global, porque as variáveis ​​declaradas usando vardentro do contexto global são adicionadas como propriedades ao objeto global:

var x = 1
console.log(window.hasOwnProperty('x')) // true

lete constno contexto global não adicione propriedades ao objeto global, mas ainda tenha escopo global:

let x = 1
console.log(window.hasOwnProperty('x')) // false

Os parâmetros de função podem ser considerados declarados no corpo da função:

function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function

Os parâmetros do bloco de captura têm escopo definido para o corpo do bloco de captura:

try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block

As expressões de função nomeadas têm escopo definido apenas para a própria expressão:

(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression

No modo não estrito, as propriedades definidas implicitamente no objeto global têm escopo global. No modo estrito, você recebe um erro.

x = 1 // implicitly defined property on the global object (no "var"!)

console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true

No modo não estrito, as declarações de função têm escopo de função. No modo estrito, eles têm escopo de bloco.

'use strict'
{
    function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped

Como funciona sob o capô

Escopo é definido como a região lexical do código sobre a qual um identificador é válido.

No JavaScript, todo objeto de função possui uma [[Environment]]referência oculta que é uma referência ao ambiente lexical do contexto de execução (quadro da pilha) no qual foi criado.

Quando você invoca uma função, o [[Call]]método oculto é chamado. Este método cria um novo contexto de execução e estabelece um link entre o novo contexto de execução e o ambiente lexical do objeto de função. Isso é feito copiando o [[Environment]]valor no objeto de função, para um campo de referência externo no ambiente lexical do novo contexto de execução.

Observe que esse link entre o novo contexto de execução e o ambiente lexical do objeto de função é chamado de fechamento .

Assim, em JavaScript, o escopo é implementado através de ambientes lexicais vinculados em uma "cadeia" por referências externas. Essa cadeia de ambientes lexicais é chamada cadeia de escopo, e a resolução do identificador ocorre pesquisando-se na cadeia por um identificador correspondente.

Saiba mais .

Tríptico
fonte
280
Nem mesmo perto de ser abrangente, mas talvez esse seja o conjunto de truques de escopo Javascript que você precisa conhecer para ler com eficácia o javascript moderno.
Triptych
148
Uma resposta altamente cotada, sem saber por quê. É apenas um monte de exemplos sem explicação adequada, e parece confundir herança de protótipo (ou seja, resolução de propriedades) com a cadeia de escopo (ou seja, resolução variável). Uma explicação abrangente (e precisa) do escopo e da resolução de propriedades está nas notas de FAQ do comp.lang.javascript .
RobG 10/09/12
109
O @RobG é altamente classificado porque é útil e compreensível para uma ampla gama de programadores, apesar da menor catacresis. O link que você postou, embora útil para alguns profissionais, é incompreensível para a maioria das pessoas que escreve Javascript hoje. Sinta-se à vontade para corrigir qualquer problema de nomenclatura editando a resposta.
Triptych
7
@ Triptych - edito apenas respostas para corrigir pequenas coisas, não importantes. Alterar "escopo" para "propriedade" corrigirá o erro, mas não a questão de misturar herança e escopo sem uma distinção muito clara.
RobG 10/09/12
24
Se você definir uma variável no escopo externo e, em seguida, tiver uma instrução if, defina uma variável dentro da função com o mesmo nome, mesmo que se a ramificação não for alcançada, ela será redefinida. Um exemplo - jsfiddle.net/3CxVm
Chris S
233

Javascript usa cadeias de escopo para estabelecer o escopo de uma determinada função. Geralmente, há um escopo global e cada função definida possui seu próprio escopo aninhado. Qualquer função definida dentro de outra função tem um escopo local que está vinculado à função externa. É sempre a posição na fonte que define o escopo.

Um elemento na cadeia de escopo é basicamente um mapa com um ponteiro para seu escopo pai.

Ao resolver uma variável, o javascript inicia no escopo mais interno e pesquisa para fora.

krosenvold
fonte
1
Cadeias de escopo são outro termo para fechamentos [de memória] ... para quem lê aqui para aprender / entrar em javascript.
New Alexandria
108

Variáveis ​​declaradas globalmente têm um escopo global. As variáveis ​​declaradas em uma função têm escopo definido para essa função e sombream variáveis ​​globais com o mesmo nome.

(Tenho certeza de que existem muitas sutilezas que os verdadeiros programadores JavaScript poderão apontar em outras respostas. Em particular, encontrei esta página sobre o que exatamente thissignifica a qualquer momento. Espero que este link mais introdutório seja suficiente para você começar .)

Jon Skeet
fonte
7
Tenho medo de começar a responder a essa pergunta. Como um programador Javascript real, sei com que rapidez a resposta pode ficar fora de controle. Artigos agradáveis.
Triptych
10
@ Triptych: Eu sei o que você quer dizer com as coisas ficando fora de controle, mas por favor, adicione uma resposta de qualquer maneira. Eu obtive o que foi dito acima apenas fazendo algumas pesquisas ... uma resposta escrita por alguém com experiência real provavelmente será melhor. Corrija qualquer resposta que esteja definitivamente errada!
911 Jon Skeet
4
De alguma forma, Jon Skeet é responsável pela MINHA resposta mais popular no Stack Overflow.
Tríptico
75

JavaScript da velha escola

Tradicionalmente, o JavaScript realmente tem apenas dois tipos de escopo:

  1. Escopo Global : As variáveis ​​são conhecidas em todo o aplicativo, desde o início do aplicativo (*)
  2. Escopo Funcional : As variáveis ​​são conhecidas dentro função em que são declaradas, desde o início da função (*)

Não vou elaborar isso, pois já existem muitas outras respostas que explicam a diferença.


JavaScript moderno

As especificações JavaScript mais recentes agora também permitem um terceiro escopo:

  1. Escopo do bloco : os identificadores são "conhecidos" na parte superior do escopo em que são declarados , mas não podem ser atribuídos ou desreferenciados (lidos) até depois da linha de sua declaração. Esse período intermediário é chamado de "zona morta temporal".

Como crio variáveis ​​de escopo de bloco?

Tradicionalmente, você cria suas variáveis ​​assim:

var myVariable = "Some text";

As variáveis ​​do escopo do bloco são criadas assim:

let myVariable = "Some text";

Então, qual é a diferença entre escopo funcional e escopo de bloco?

Para entender a diferença entre o escopo funcional e o escopo do bloco, considere o seguinte código:

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

Aqui, podemos ver que nossa variável jé conhecida apenas no primeiro loop for, mas não antes e depois. No entanto, nossa variáveli é conhecida em toda a função.

Além disso, considere que as variáveis ​​com escopo de bloco não são conhecidas antes de serem declaradas porque não são içadas. Também não é permitido redefinir a mesma variável com escopo de bloco dentro do mesmo bloco. Isso torna as variáveis ​​com escopo do bloco menos propensas a erros do que as variáveis ​​com escopo global ou funcional, que são içadas e que não produzem erros no caso de várias declarações.


É seguro usar variáveis ​​de escopo de bloco hoje?

Se é seguro usar ou não hoje, depende do seu ambiente:

  • Se você estiver escrevendo o código JavaScript do servidor ( Node.js ), poderá usar a letinstrução com segurança .

  • Se você estiver escrevendo código JavaScript do lado do cliente e usar um transpiler baseado em navegador (como Traceur ou babel-standalone ), poderá usar a letinstrução com segurança , no entanto, é provável que seu código seja qualquer coisa, menos ideal em relação ao desempenho.

  • Se você estiver escrevendo código JavaScript do lado do cliente e usar um transpiler baseado em Nó (como o script shell de rastreamento ou Babel ), poderá usar a letinstrução com segurança . E como o seu navegador só conhece o código transpilado, as desvantagens do desempenho devem ser limitadas.

  • Se você estiver escrevendo um código JavaScript do lado do cliente e não usa um transpiler, considere o suporte ao navegador.

    Estes são alguns navegadores que não oferecem suporte let:

    • Internet Explorer 10 e inferior
    • Firefox 43 e abaixo
    • Safari 9 e abaixo
    • Navegador Android 4 e abaixo
    • Opera 27 e abaixo
    • Chome 40 e abaixo
    • QUALQUER versão do Opera Mini & Blackberry Browser

insira a descrição da imagem aqui


Como acompanhar o suporte ao navegador

Para uma visão geral atualizada de quais navegadores suportam a letdeclaração no momento da leitura desta resposta, consulte esta Can I Usepágina .


(*) Variáveis ​​com escopo global e funcional podem ser inicializadas e usadas antes de serem declaradas porque as variáveis ​​JavaScript são hasteadas . Isso significa que as declarações sempre estão no topo do escopo.

John Slegers
fonte
2
"NÃO É conhecido" é enganoso, porque a variável é declarada lá devido à elevação.
Oriol
O exemplo acima é enganoso, as variáveis ​​'i' e 'j' não são conhecidas fora do bloco. As variáveis ​​'Let' têm escopo apenas nesse bloco em particular e não fora dele. Let também tem outras vantagens: você não pode redeclarar a variável novamente e ela mantém o escopo lexical.
zakir
1
Isso foi útil, obrigado! Eu acho que seria ainda mais útil ser específico sobre o que você quer dizer com "Modern JavaScript" e "Old school JavaScript"; Penso que estes correspondem ao ECMAScript 6 / ES6 / ECMAScript 2015 e a versões anteriores, respectivamente?
9118 Jon Schneider #:
1
@ JonSchneider: Correto! Onde digo "JavaScript da velha escola", estou falando sobre o ECMAScript 5 e, quando estou me referindo ao "JavaScript moderno", estou abordando o ECMAScript 6 (também conhecido como ECMAScript 2015). Porém, não achei que fosse tão importante entrar em detalhes aqui, pois a maioria das pessoas quer saber (1) qual a diferença entre o escopo do bloco e o escopo funcional, (2) quais navegadores suportam o escopo do bloco e (3) se é seguro usar o escopo do bloco hoje para qualquer projeto em que eles estejam trabalhando. Então, concentrei minha resposta em resolver esses problemas.
John Slegers
1
@ JonSchneider: (continuação) No entanto, acabei de adicionar um link para um artigo da Smashing Magazine no ES6 / ES2015 para aqueles que desejam aprender mais sobre quais recursos foram adicionados ao JavaScript nos últimos dois anos ... de qualquer pessoa que pode estar se perguntando o que quero dizer com "JavaScript moderno".
John Slegers
39

Aqui está um exemplo:

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

Você deseja investigar os fechamentos e como usá-los para criar membros privados .

geowa4
fonte
31

A chave, pelo que entendi, é que o Javascript tem escopo no nível de função versus o escopo do bloco C mais comum.

Aqui está um bom artigo sobre o assunto.

James McMahon
fonte
26

Em "Javascript 1.7" (extensão do Mozilla para Javascript) também é possível declarar variáveis ​​de escopo de bloco com a letinstrução :

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4
kennytm
fonte
2
Sim, mas é seguro usar? Quero dizer, eu escolheria realisticamente essa implementação se meu código fosse executado no WebKit?
IgorGanapolsky
10
@ Python: Não, o WebKit não suporta let.
Kennytm
Eu acho que o único uso válido para isso seria se você soubesse que todos os clientes usariam um navegador Mozilla como para o sistema interno de uma empresa.
GazB 11/11/12
Ou se você estiver programando usando a estrutura XUL, a estrutura de interface do Mozilla onde você constrói usando css, xml e javascript.
precisa
1
@GazB mesmo que seja uma idéia horrível! Então hoje você sabe que seus clientes estão usando o Mozilla e, em seguida, sai uma nova nota informando que agora eles estão usando outra coisa. IE, a razão pela qual nosso sistema de pagamento é péssimo ... Você deve usar o IE8 e nunca o IE9 ou IE10 ou Firefox ou Chrome, porque ele
simplesmente
25

A ideia do escopo do JavaScript, quando originalmente projetada por Brendan Eich, veio da linguagem de script HyperCard HyperTalk .

Nesse idioma, as exibições eram feitas de maneira semelhante a uma pilha de cartões de índice. Havia um cartão mestre conhecido como plano de fundo. Era transparente e pode ser visto como o cartão inferior. Qualquer conteúdo deste cartão base foi compartilhado com os cartões colocados em cima dele. Cada cartão colocado em cima tinha seu próprio conteúdo, que prevalecia sobre o cartão anterior, mas ainda assim tinha acesso aos cartões anteriores, se desejado.

É exatamente assim que o sistema de escopo do JavaScript é projetado. Só tem nomes diferentes. Os cartões em JavaScript são conhecidos como Execution Contexts ECMA . Cada um desses contextos contém três partes principais. Um ambiente variável, um ambiente lexical e essa ligação. Voltando à referência de cartões, o ambiente lexical contém todo o conteúdo dos cartões anteriores, mais abaixo na pilha. O contexto atual está no topo da pilha e qualquer conteúdo declarado lá será armazenado no ambiente variável. O ambiente variável terá precedência no caso de colisões de nomes.

A ligação this apontará para o objeto que contém. Às vezes, os escopos ou os contextos de execução são alterados sem que o objeto contido seja alterado, como em uma função declarada em que o objeto contido pode estar windowou uma função construtora.

Esses contextos de execução são criados sempre que o controle é transferido. O controle é transferido quando o código começa a ser executado, e isso é feito principalmente a partir da execução da função.

Então essa é a explicação técnica. Na prática, é importante lembrar que em JavaScript

  • Os escopos são tecnicamente "contextos de execução"
  • Os contextos formam uma pilha de ambientes onde as variáveis ​​são armazenadas
  • A parte superior da pilha tem precedência (a parte inferior é o contexto global)
  • Cada função cria um contexto de execução (mas nem sempre é uma nova ligação)

Aplicando isso a um dos exemplos anteriores (5. "Fechamento") nesta página, é possível seguir a pilha de contextos de execução. Neste exemplo, existem três contextos na pilha. Eles são definidos pelo contexto externo, o contexto na função chamada imediatamente chamada por var six e o contexto na função retornada dentro da função chamada imediatamente pela var six.

i ) O contexto externo. Possui um ambiente variável de a = 1
ii ) O contexto IIFE, possui um ambiente lexical de a = 1, mas um ambiente variável de a = 6 que tem precedência na pilha
iii ) O contexto de função retornado possui um léxico ambiente de a = 6 e esse é o valor referenciado no alerta quando chamado.

insira a descrição da imagem aqui

Travis J
fonte
17

1) Existe um escopo global, um escopo de função e os escopos with e catch. Em geral, não existe um escopo de nível de 'bloco' para as variáveis ​​- as instruções with e catch adicionam nomes aos seus blocos.

2) Os escopos são aninhados por funções até o escopo global.

3) As propriedades são resolvidas passando pela cadeia de protótipos. A instrução with traz nomes de propriedades do objeto para o escopo lexical definido pelo bloco with.

EDIT: ECMAAScript 6 (Harmony) é especificado para suportar let, e eu sei que o chrome permite uma flag de 'harmonia', então talvez ele o suporte ..

Let seria um suporte para o escopo no nível do bloco, mas você precisa usar a palavra-chave para que isso aconteça.

EDIT: Com base no fato de Benjamin apontar as declarações with e catch nos comentários, editei o post e adicionei mais. As instruções with e catch introduzem variáveis ​​em seus respectivos blocos, e esse é um escopo de bloco. Essas variáveis ​​são alias às propriedades dos objetos passados ​​para elas.

 //chrome (v8)

 var a = { 'test1':'test1val' }
 test1   // error not defined
 with (a) { var test1 = 'replaced' }
 test1   // undefined
 a       // a.test1 = 'replaced'

EDIT: Exemplo esclarecedor:

O teste1 tem como escopo o bloco com, mas é alias para a.test1. 'Var test1' cria uma nova variável test1 no contexto lexical superior (função ou global), a menos que seja uma propriedade de a - como é.

Caramba! Cuidado ao usar 'with' - assim como var é um noop se a variável já estiver definida na função, também é um noop com relação aos nomes importados do objeto! Um pouco de atenção no nome já sendo definido tornaria isso muito mais seguro. Eu pessoalmente nunca vou usar por causa disso.

Gerard ONeill
fonte
Você tem alguns erros aqui, pois um JavaScript tem formas de escopo de bloco.
Benjamin Gruenbaum 25/10/2013
Meus ouvidos (olhos) estão abertos, Benjamin - Minhas declarações acima são como tenho tratado o escopo do Javascript, mas elas não se baseiam na leitura das especificações. E espero que você não esteja se referindo à declaração with (que é uma forma de escopo de objeto) ou à sintaxe especial 'let' do Mozilla.
precisa
Bem, a withdeclaração é uma forma de escopo de bloco, mas as catchcláusulas são uma forma muito mais comum (fato divertido, a v8 implementa catchcom a with) - são praticamente as únicas formas de escopo de bloco no próprio JavaScript (ou seja, função, global, try / catch , com e seus derivados), no entanto, os ambientes host possuem diferentes noções de escopo - por exemplo, eventos embutidos no navegador e no módulo vm do NodeJS.
Benjamin Gruenbaum 25/10
Benjamin - pelo que posso ver, tanto com como com catch apenas introduzem o objeto no escopo atual (e, portanto, nas propriedades), mas depois que o respectivo bloco termina, as variáveis ​​são redefinidas. Mas, por exemplo, uma nova variável introduzida em uma captura terá o escopo da função / método envolvente.
Gerard ONeill
2
Que é exatamente o meio bloco de escopo :)
Benjamin Gruenbaum
9

Descobri que muitas pessoas novas no JavaScript têm problemas para entender que a herança está disponível por padrão no idioma e que o escopo da função é o único escopo até agora. Forneci uma extensão para um embelezador que escrevi no final do ano passado chamado JSPretty. As cores do recurso funcionam com o escopo no código e sempre associam uma cor a todas as variáveis ​​declaradas nesse escopo. O fechamento é demonstrado visualmente quando uma variável com uma cor de um escopo é usada em um escopo diferente.

Experimente o recurso em:

Veja uma demonstração em:

Veja o código em:

Atualmente, o recurso oferece suporte para uma profundidade de 16 funções aninhadas, mas atualmente não colore variáveis ​​globais.

austincheney
fonte
1
Não funciona para mim no Firefox 26. Colo código ou carrego um arquivo, clique em executar e nada acontece.
Mplwork
Escopo e herança são duas coisas diferentes.
Ben Aston
9

JavaScript tem apenas dois tipos de escopo:

  1. Escopo Global : Global nada mais é do que um escopo no nível da janela. Aqui, variável presente em todo o aplicativo.
  2. Escopo Funcional : Variável declarada dentro de uma função comvar palavra-chave tem escopo funcional.

Sempre que uma função é chamada, um objeto de escopo variável é criado (e incluído na cadeia de escopo) que é seguido por variáveis ​​em JavaScript.

        a = "global";
         function outer(){ 
              b = "local";
              console.log(a+b); //"globallocal"
         }
outer();

Cadeia de escopo ->

  1. Nível da janela - a e outerfunção estão no nível superior da cadeia de escopo.
  2. quando a função externa chamou um novo variable scope object(e incluído na cadeia de escopo) adicionado com variávelb dentro dela.

Agora, quando uma variável arequerida, ele primeiro procura o escopo da variável mais próximo e, se a variável não estiver lá, passa para o próximo objeto da cadeia de escopo da variável., Que nesse caso é o nível da janela.

Anshul
fonte
1
Não sei por que essa não é a resposta aceita. Na verdade, existe apenas escopo funcional (antes do ECMA6 não havia "escopo local") e ligações globais
texasbruce 28/02/2015
9

Apenas para adicionar às outras respostas, o escopo é uma lista de pesquisa de todos os identificadores declarados (variáveis) e aplica um conjunto estrito de regras sobre como elas são acessíveis ao código atualmente em execução. Essa pesquisa pode ser para fins de atribuição à variável, que é uma referência LHS (lado esquerdo), ou pode ser para fins de recuperação de seu valor, que é uma referência RHS (lado direito). Essas pesquisas são o que o mecanismo JavaScript está fazendo internamente quando está compilando e executando o código.

Então, sob essa perspectiva, acho que uma imagem ajudaria o que encontrei no ebook Scopes and Closures de Kyle Simpson:

imagem

Citando seu ebook:

O edifício representa o conjunto de regras de escopo aninhado do nosso programa. O primeiro andar do edifício representa seu escopo em execução no momento, onde você estiver. O nível superior do edifício é o escopo global. Você resolve as referências de LHS e RHS olhando em seu andar atual e, se não o encontrar, pegue o elevador para o próximo andar, procure lá, depois o próximo e assim por diante. Depois de chegar ao último andar (o escopo global), você encontra o que está procurando ou não. Mas você tem que parar de qualquer maneira.

Vale ressaltar que "a pesquisa de escopo para quando encontra a primeira correspondência".

Essa idéia de "níveis de escopo" explica por que "isso" pode ser alterado com um escopo recém-criado, se estiver sendo procurado em uma função aninhada. Aqui está um link que aborda todos esses detalhes. Tudo o que você queria saber sobre o escopo do javascript

James Drinkard
fonte
8

execute o código. espero que isso dê uma idéia sobre o escopo

Name = 'global data';
document.Name = 'current document data';
(function(window,document){
var Name = 'local data';
var myObj = {
    Name: 'object data',
    f: function(){
        alert(this.Name);
    }
};

myObj.newFun = function(){
    alert(this.Name);
}

function testFun(){
    alert("Window Scope : " + window.Name + 
          "\nLocal Scope : " + Name + 
          "\nObject Scope : " + this.Name + 
          "\nCurrent document Scope : " + document.Name
         );
}


testFun.call(myObj);
})(window,document);
Yeasin Abedin Siam
fonte
8

Âmbito global :

Variáveis ​​globais são exatamente como estrelas globais (Jackie Chan, Nelson Mandela). Você pode acessá-los (obter ou definir o valor), a partir de qualquer parte do seu aplicativo. Funções globais são como eventos globais (Ano Novo, Natal). Você pode executá-las (chamá-las) de qualquer parte do seu aplicativo.

//global variable
var a = 2;

//global function
function b(){
   console.log(a);  //access global variable
}

Escopo local:

Se você está nos EUA, talvez conheça Kim Kardashian, celebridade infame (ela de alguma forma consegue fazer os tablóides). Mas pessoas fora dos EUA não a reconhecerão. Ela é uma estrela local, ligada ao seu território.

Variáveis ​​locais são como estrelas locais. Você só pode acessá-los (obter ou definir o valor) dentro do escopo. Uma função local é como eventos locais - você pode executar apenas (comemorar) dentro desse escopo. Se você deseja acessá-los fora do escopo, receberá um erro de referência

function b(){
   var d = 21; //local variable
   console.log(d);

   function dog(){  console.log(a); }
     dog(); //execute local function
}

 console.log(d); //ReferenceError: dddddd is not defined    

Confira este artigo para entender profundamente o escopo

Jhankar Mahbub
fonte
6

Existem QUASE apenas dois tipos de escopos de JavaScript:

  • o escopo de cada declaração var está associado à função de fechamento mais imediata
  • se não houver função anexa para uma declaração var, é escopo global

Portanto, quaisquer blocos que não sejam funções não criam um novo escopo. Isso explica por que os loops de substituição substituem as variáveis ​​com escopo externo:

var i = 10, v = 10;
for (var i = 0; i < 5; i++) { var v = 5; }
console.log(i, v);
// output 5 5

Em vez disso, usando funções:

var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10

No primeiro exemplo, não havia escopo de bloco, portanto, as variáveis ​​declaradas inicialmente foram substituídas. No segundo exemplo, havia um novo escopo devido à função; portanto, as variáveis ​​declaradas inicialmente eram SOMBRA e não substituídas.

Isso é quase tudo o que você precisa saber em termos de escopo do JavaScript, exceto:

Assim, você pode ver que o escopo do JavaScript é realmente extremamente simples, embora nem sempre seja intuitivo. Algumas coisas a ter em atenção:

  • declarações var são hasteadas no topo do escopo. Isso significa que não importa onde a declaração var acontece, para o compilador é como se a própria var acontecesse no topo
  • várias declarações var dentro do mesmo escopo são combinadas

Portanto, este código:

var i = 1;
function abc() {
  i = 2;
  var i = 3;
}
console.log(i);     // outputs 1

é equivalente a:

var i = 1;
function abc() {
  var i;     // var declaration moved to the top of the scope
  i = 2;
  i = 3;     // the assignment stays where it is
}
console.log(i);

Isso pode parecer contra-intuitivo, mas faz sentido da perspectiva de um designer de linguagem imperativo.

jackbean818
fonte
5

Js modernos, ES6 +, ' const' e ' let'

Você deve usar o escopo do bloco para todas as variáveis ​​criadas, assim como a maioria dos outros idiomas principais. varé obsoleto . Isso torna seu código mais seguro e mais sustentável.

constdeve ser usado em 95% dos casos . Faz com que a referência da variável não possa ser alterada. A matriz, o objeto e as propriedades do nó DOM podem mudar e provavelmente devem estar const.

letdeve ser usado para qualquer variável que espera ser reatribuída. Isso inclui um loop for. Se você alterar algum valor além da inicialização, use let.

O escopo do bloco significa que a variável estará disponível apenas dentro dos colchetes em que é declarada. Isso se estende aos escopos internos, incluindo funções anônimas criadas dentro do seu escopo.

Gibolt
fonte
3

Tente este exemplo curioso. No exemplo abaixo, se a fosse um numérico inicializado em 0, você veria 0 e, em seguida, 1. Exceto a é um objeto e o javascript passará f1 um ponteiro de um em vez de uma cópia dele. O resultado é que você recebe o mesmo alerta as duas vezes.

var a = new Date();
function f1(b)
{
    b.setDate(b.getDate()+1);
    alert(b.getDate());
}
f1(a);
alert(a.getDate());
Mig82
fonte
3

Existem apenas escopos de função em JS. Não bloqueie os escopos! Você também pode ver o que está içando.

var global_variable = "global_variable";
var hoisting_variable = "global_hoist";

// Global variables printed
console.log("global_scope: - global_variable: " + global_variable);
console.log("global_scope: - hoisting_variable: " + hoisting_variable);

if (true) {
    // The variable block will be global, on true condition.
    var block = "block";
}
console.log("global_scope: - block: " + block);

function local_function() {
    var local_variable = "local_variable";
    console.log("local_scope: - local_variable: " + local_variable);
    console.log("local_scope: - global_variable: " + global_variable);
    console.log("local_scope: - block: " + block);
    // The hoisting_variable is undefined at the moment.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);

    var hoisting_variable = "local_hoist";
    // The hoisting_variable is now set as a local one.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);
}

local_function();

// No variable in a separate function is visible into the global scope.
console.log("global_scope: - local_variable: " + local_variable);
koredalin
fonte
(muito tempo desde a resposta publicada) Bloquear escopo; developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Bob
2

Meu entendimento é que existem três escopos: escopo global, disponível globalmente; escopo local, disponível para uma função inteira, independentemente de blocos; e escopo do bloco, disponível apenas para o bloco, instrução ou expressão em que foi usado. O escopo global e local é indicado com a palavra-chave 'var', dentro de uma função ou fora, e o escopo do bloco é indicado com a palavra-chave 'let'.

Para aqueles que acreditam que há apenas escopo global e local, explique por que a Mozilla teria uma página inteira descrevendo as nuances do escopo do bloco em JS.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

mrmaclean89
fonte
2

Um problema muito comum ainda não descrito, no qual os codificadores de front-end frequentemente se deparam é o escopo visível para um manipulador de eventos embutido no HTML - por exemplo, com

<button onclick="foo()"></button>

O escopo das variáveis ​​às quais um on*atributo pode fazer referência deve ser:

  • global (os manipuladores inline trabalhando quase sempre fazem referência a variáveis ​​globais)
  • uma propriedade do documento (por exemplo, querySelectorcomo uma variável autônoma apontará para document.querySelector; raro)
  • uma propriedade do elemento ao qual o manipulador está anexado (como acima; raro)

Caso contrário, você obterá um ReferenceError quando o manipulador for chamado. Portanto, por exemplo, se o manipulador inline fizer referência a uma função definida dentro de window.onload ou $(function() {, a referência falhará, porque o manipulador inline pode fazer referência apenas a variáveis ​​no escopo global e a função não é global:

As propriedades documente as propriedades do elemento ao qual o manipulador está anexado também podem ser referenciadas como variáveis ​​independentes dentro de manipuladores inline, pois os manipuladores inline são chamados dentro de dois withblocos , um para o documente outro para o elemento. A cadeia de escopo de variáveis ​​dentro desses manipuladores é extremamente pouco intuitiva , e um manipulador de eventos em funcionamento provavelmente exigirá que uma função seja global (e provavelmente a poluição global desnecessária deve ser evitada ).

Como a cadeia de escopo dentro dos manipuladores inline é muito estranha , e como os manipuladores inline exigem que a poluição global funcione, e como os manipuladores inline às vezes exigem uma fuga feia de caracteres ao passar argumentos, provavelmente é mais fácil evitá-los. Em vez disso, anexe manipuladores de eventos usando Javascript (como com addEventListener), em vez de com marcação HTML.


Em uma observação diferente, diferentemente das <script>tags normais , que são executadas no nível superior, o código dentro dos módulos ES6 é executado em seu próprio escopo privado. Uma variável definida na parte superior de uma <script>tag normal é global, portanto, você pode referenciá-la em outras <script>tags, como esta:

Mas o nível superior de um módulo ES6 não é global. Uma variável declarada na parte superior de um módulo ES6 será visível apenas dentro desse módulo, a menos que a variável seja explicitamente exporteditada ou a menos que esteja atribuída a uma propriedade do objeto global.

O nível superior de um módulo ES6 é semelhante ao do interior de um IIFE no nível superior normalmente <script>. O módulo pode fazer referência a quaisquer variáveis ​​globais, e nada pode fazer referência a nada dentro do módulo, a menos que o módulo seja projetado explicitamente para ele.

CertainPerformance
fonte
1

No JavaScript, existem dois tipos de escopo:

  • Escopo local
  • Âmbito global

A função Abaixo tem uma variável de escopo local carName. E essa variável não é acessível de fora da função.

function myFunction() {
    var carName = "Volvo";
    alert(carName);
    // code here can use carName
}

A classe Abaixo tem uma variável de escopo Global carName. E essa variável é acessível de qualquer lugar da classe.

class {

    var carName = " Volvo";

    // code here can use carName

    function myFunction() {
        alert(carName);
        // code here can use carName 
    }
}
Abdur Rahman
fonte
1

ES5 e anterior:

Variáveis ​​em Javascript foram inicialmente (pré ES6) lexicamente definidas no escopo da função. O termo com escopo lexical significa que você pode ver o escopo das variáveis ​​'observando' o código.

Toda variável declarada com a varpalavra-chave tem o escopo definido para a função No entanto, se outra função for declarada nessa função, essas funções terão acesso às variáveis ​​das funções externas. Isso é chamado de cadeia de escopo . Funciona da seguinte maneira:

  1. Quando uma função procura resolver um valor de variável, primeiro analisa seu próprio escopo. Este é o corpo da função, ou seja, tudo entre colchetes {} (exceto para variáveis ​​dentro de outras funções que estão neste escopo).
  2. Se não conseguir encontrar a variável dentro do corpo da função, ela subirá para a cadeia e examinará o escopo da variável na função em que a função foi definida . Isto é o que se entende com escopo lexical, podemos ver no código onde essa função foi definida e, portanto, podemos determinar a cadeia de escopo simplesmente olhando o código.

Exemplo:

// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';

function outerFunc () {
 // outerFunc scope
 var foo = 'outerFunc';
 var foobar = 'outerFunc';
 innerFunc();
 
 function innerFunc(){
 // innerFunc scope
  var foo = 'innerFunc';
  console.log(foo);
  console.log(bar);
  console.log(foobar);
  }
}

outerFunc();

O que acontece quando estamos tentando registrar as variáveis foo, bare foobarpara o console é o seguinte:

  1. Tentamos logar foo no console, foo pode ser encontrado dentro da innerFuncprópria função . Portanto, o valor de foo é resolvido para a sequência innerFunc.
  2. Tentamos registrar a barra no console, a barra não pode ser encontrada dentro da innerFuncprópria função . Portanto, precisamos escalar a cadeia de escopo . Primeiro, examinamos a função externa na qual a função innerFuncfoi definida. Essa é a função outerFunc. No escopo deouterFunc , podemos encontrar a barra variável, que contém a string 'outerFunc'.
  3. foobar não pode ser encontrado em innerFunc. . Portanto, precisamos escalar a cadeia de escopo para o escopo innerFunc. Também não pode ser encontrado aqui, subimos outro nível para o escopo global (ou seja, o escopo mais externo). Encontramos aqui a variável foobar que contém a string 'global'. Se não tivesse encontrado a variável após escalar a cadeia de escopo, o mecanismo JS lançaria um referenceError .

ES6 (ES 2015) e mais antigos:

Os mesmos conceitos de escopo e cadeia de expressão lexicamente ainda se aplicam ES6. No entanto, foram introduzidas novas maneiras de declarar variáveis. Existem os seguintes:

  • let: cria uma variável com escopo definido em bloco
  • const: cria uma variável com escopo de bloco que precisa ser inicializada e não pode ser reatribuída

A maior diferença entre vare let/ consté o varescopo da função, enquanto let/ consté o escopo do bloco. Aqui está um exemplo para ilustrar isso:

let letVar = 'global';
var varVar = 'global';

function foo () {
  
  if (true) {
    // this variable declared with let is scoped to the if block, block scoped
    let letVar = 5;
    // this variable declared with let is scoped to the function block, function scoped
    var varVar = 10;
  }
  
  console.log(letVar);
  console.log(varVar);
}


foo();

No exemplo acima, letVar registra o valor global porque as variáveis ​​declaradas com lettêm escopo de bloco. Eles deixam de existir fora do respectivo bloco, portanto, a variável não pode ser acessada fora do bloco if.

Willem van der Veen
fonte
0

No EcmaScript5, existem principalmente dois escopos, escopo local e escopo global, mas no EcmaScript6 temos três escopos, escopo local, escopo global e um novo escopo chamado escopo de bloco .

Exemplo de escopo de bloco é: -

for ( let i = 0; i < 10; i++)
{
 statement1...
statement2...// inside this scope we can access the value of i, if we want to access the value of i outside for loop it will give undefined.
}
Vivek Mehta
fonte
0

O ECMAScript 6 introduziu as palavras-chave let e const. Essas palavras-chave podem ser usadas no lugar da palavra-chave var. Ao contrário da palavra-chave var, as palavras-chave let e const suportam a declaração do escopo local dentro das instruções do bloco.

var x = 10
let y = 10
const z = 10
{
  x = 20
  let y = 20
  const z = 20
  {
    x = 30
    // x is in the global scope because of the 'var' keyword
    let y = 30
    // y is in the local scope because of the 'let' keyword
    const z = 30
    // z is in the local scope because of the 'const' keyword
    console.log(x) // 30
    console.log(y) // 30
    console.log(z) // 30
  }
  console.log(x) // 30
  console.log(y) // 20
  console.log(z) // 20
}

console.log(x) // 30
console.log(y) // 10
console.log(z) // 10
Davaakhuu Erdenekhuu
fonte
0

Eu realmente gosto da resposta aceita, mas quero adicionar isso:

O escopo coleta e mantém uma lista de pesquisa de todos os identificadores declarados (variáveis) e aplica um conjunto estrito de regras sobre como elas são acessíveis ao código atualmente em execução.

Escopo é um conjunto de regras para procurar variáveis ​​pelo nome do identificador.

  • Se uma variável não puder ser encontrada no escopo imediato, o Engine consultará o próximo escopo externo, continuando até que seja encontrado ou até que o escopo mais externo (aka, global) seja atingido.
  • É o conjunto de regras que determina onde e como uma variável (identificador) pode ser pesquisada. Essa pesquisa pode ser para fins de atribuição à variável, que é uma referência LHS (lado esquerdo) ou para recuperar seu valor, que é uma referência RHS (lado direito) .
  • As referências LHS resultam de operações de atribuição. As atribuições relacionadas ao escopo podem ocorrer com o operador = ou passando argumentos para (atribuir a) parâmetros de função.
  • O mecanismo JavaScript primeiro compila o código antes de executar e, ao fazer isso, divide instruções como var a = 2; em duas etapas separadas: 1º. Primeiro, var a para declarar nesse escopo. Isso é realizado no início, antes da execução do código. 2nd. Posteriormente, a = 2 para procurar a variável (referência LHS) e atribuí-la se encontrada.
  • As pesquisas de referência do LHS e do RHS começam no escopo em execução no momento e, se necessário (ou seja, elas não encontram o que estão procurando lá), elas avançam no escopo aninhado, um escopo (floor ) de cada vez, procurando o identificador, até que cheguem ao global (último andar) e parem, e o encontrem ou não. As referências não preenchidas do RHS resultam no lançamento do ReferenceError. As referências não preenchidas do LHS resultam em um global automático, implicitamente criado, com esse nome (se não estiver no modo estrito) ou em um ReferenceError (se estiver no modo estrito).
  • O escopo consiste em uma série de "bolhas" que atuam como um contêiner ou balde, no qual os identificadores (variáveis, funções) são declarados. Essas bolhas aninham-se perfeitamente umas nas outras e esse aninhamento é definido no momento do autor.
Ahmed KhaShaba
fonte
-3

Existem dois tipos de escopos no JavaScript.

  1. Escopo global : a variável anunciada no escopo global pode ser usada em qualquer lugar do programa de maneira muito suave. Por exemplo:

    var carName = " BMW";
    
    // code here can use carName
    
    function myFunction() {
         // code here can use carName 
    }
  2. Escopo funcional ou Escopo local : a variável declarada neste escopo pode ser usada apenas em sua própria função. Por exemplo:

    // code here can not use carName
    function myFunction() {
       var carName = "BMW";
       // code here can use carName
    }
A. Randhawa
fonte