Какова область видимости переменных в JavaScript? - PullRequest
1889 голосов
/ 01 февраля 2009

Какова область видимости переменных в javascript? Имеют ли они одинаковую область внутри, в отличие от внешней функции? Или это вообще имеет значение? Кроме того, где хранятся переменные, если они определены глобально?

Ответы [ 24 ]

2413 голосов
/ 01 февраля 2009

Я думаю, что лучшее, что я могу сделать, это дать вам кучу примеров для изучения. Программисты Javascript практически ранжируются по тому, насколько хорошо они понимают сферу. Иногда это может быть довольно нелогичным.

  1. Глобальная переменная

    // global scope
    var a = 1;
    
    function one() {
      alert(a); // alerts '1'
    }
    
  2. Локальный охват

    // global scope
    var a = 1;
    
    function two(a) { // passing (a) makes it local scope
      alert(a); // alerts the given argument, not the global value of '1'
    }
    
    // local scope again
    function three() {
      var a = 3;
      alert(a); // alerts '3'
    }
    
  3. Промежуточный : Нет такой вещи, как область видимости блока в JavaScript (ES5; ES6 представляет let)

    а.

    var a = 1;
    
    function four() {
      if (true) {
        var a = 4;
      }
    
      alert(a); // alerts '4', not the global value of '1'
    }
    

    б.

    var a = 1;
    
    function one() {
      if (true) {
        let a = 4;
      }
    
      alert(a); // alerts '1' because the 'let' keyword uses block scoping
    }
    
  4. Средний : Свойства объекта

    var a = 1;
    
    function Five() {
      this.a = 5;
    }
    
    alert(new Five().a); // alerts '5'
    
  5. Дополнительно : Закрытие

    var a = 1;
    
    var six = (function() {
      var a = 6;
    
      return function() {
        // JavaScript "closure" means I have access to 'a' in here,
        // because it is defined in the function in which I was defined.
        alert(a); // alerts '6'
      };
    })();
    
  6. Дополнительно : Разрешение области действия на основе прототипа

    var a = 1;
    
    function seven() {
      this.a = 7;
    }
    
    // [object].prototype.property loses to
    // [object].property in the lookup chain. For example...
    
    // Won't get reached, because 'a' is set in the constructor above.
    seven.prototype.a = -1;
    
    // Will get reached, even though 'b' is NOT set in the constructor.
    seven.prototype.b = 8;
    
    alert(new seven().a); // alerts '7'
    alert(new seven().b); // alerts '8'
    

  7. Глобальный + Локальный : Очень сложный кейс

    var x = 5;
    
    (function () {
        console.log(x);
        var x = 10;
        console.log(x); 
    })();
    

    Это выведет undefined и 10, а не 5 и 10, поскольку JavaScript всегда перемещает объявления переменных (не инициализации) в верхнюю часть области, делая код эквивалентным:

    var x = 5;
    
    (function () {
        var x;
        console.log(x);
        x = 10;
        console.log(x); 
    })();
    
  8. Переменная Catch-scoped

    var e = 5;
    console.log(e);
    try {
        throw 6;
    } catch (e) {
        console.log(e);
    }
    console.log(e);
    

    Это распечатает 5, 6, 5. Внутри оператора catch e скрывает глобальные и локальные переменные. Но эта специальная область предназначена только для перехваченной переменной. Если вы напишите var f; внутри предложения catch, то это точно так же, как если бы вы определили его до или после блока try-catch.

232 голосов
/ 01 февраля 2009

Javascript использует цепочки областей действия, чтобы установить область действия для данной функции. Обычно существует одна глобальная область, и каждая определенная функция имеет свою собственную вложенную область. Любая функция, определенная в другой функции, имеет локальную область видимости, которая связана с внешней функцией. Это всегда позиция в источнике, который определяет область действия.

Элементом в цепочке областей действия в основном является Карта с указателем на родительскую область.

При разрешении переменной javascript начинается с самой внутренней области и выполняет поиск вне.

105 голосов
/ 01 февраля 2009

Переменные, объявленные глобально, имеют глобальную область видимости. Переменные, объявленные внутри функции, относятся к этой функции и скрывают глобальные переменные с тем же именем.

(Я уверен, что есть много тонкостей, которые настоящие программисты на JavaScript смогут указать в других ответах. В частности, я сталкивался с на этой странице о том, что именно означает this в любое время. Надеюсь, этой более вводной ссылки достаточно, чтобы начать работу.)

70 голосов
/ 23 февраля 2016

Старая школа JavaScript

Традиционно JavaScript действительно имеет только два типа области действия:

  1. Global Scope : Переменные известны в приложении с самого начала приложения (*)
  2. Функциональная область : переменные известны в функции , в которой они объявлены, с начала функции (*)

Я не буду подробно останавливаться на этом, поскольку уже есть много других ответов, объясняющих разницу.


Современный JavaScript

Самые последние спецификации JavaScript теперь также допускают третью область:

  1. Область действия блока : переменные известны в блоке , в котором они объявлены, с момента их объявления (**)

Как создать переменные области видимости блока?

Традиционно вы создаете переменные следующим образом:

var myVariable = "Some text";

Переменные области видимости создаются следующим образом:

let myVariable = "Some text";

Так в чем же разница между функциональной областью и областью блока?

Чтобы понять разницу между функциональной областью и областью блоков, рассмотрим следующий код:

// 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

Здесь мы видим, что наша переменная j известна только в первом цикле for, но не до и после. Тем не менее, наша переменная i известна во всей функции.

Кроме того, учтите, что переменные в области блока не известны до того, как объявлены, потому что они не подняты. Вам также не разрешается повторно объявлять одну и ту же переменную области действия блока в одном и том же блоке. Это делает переменные области блока менее подверженными ошибкам, чем переменные глобальной или функциональной области, которые поднимаются и не приводят к ошибкам в случае нескольких объявлений.


Безопасно ли сегодня использовать переменные области видимости блока?

Безопасно ли сегодня использовать, зависит от вашей среды:

  • Если вы пишете код JavaScript на стороне сервера ( Node.js ), вы можете смело использовать оператор let.

  • Если вы пишете код JavaScript на стороне клиента и используете браузер на основе транспилятора (например, Traceur или babel-standalone ), вы можете смело использовать оператор let, однако ваш код может быть не оптимальным с точки зрения производительности.

  • Если вы пишете код JavaScript на стороне клиента и используете транспортер на основе Node (например, сценарий оболочки traceur или Babel ), вы можете смело использовать оператор let. А поскольку ваш браузер будет знать только о переданном коде, недостатки производительности должны быть ограничены.

  • Если вы пишете код JavaScript на стороне клиента и не используете транспортер, вам следует подумать о поддержке браузера.

    Это некоторые браузеры, которые вообще не поддерживают let:

    • Internet Explorer 10 и ниже
    • Firefox 43 и ниже
    • Safari 9 и ниже
    • Android-браузер 4 и ниже
    • Опера 27 и ниже
    • Chome 40 и ниже
    • ЛЮБАЯ версия Opera Mini & Blackberry Browser

enter image description here


Как отслеживать поддержку браузера

Обновленный обзор того, какие браузеры поддерживают оператор let на момент прочтения этого ответа, см. На этой Can I Use странице .


(*) Глобальные и функциональные переменные могут быть инициализированы и использованы до их объявления, потому что переменные JavaScript hoisted . Это означает, что объявления всегда много к вершине области.

(**) Переменные области видимости не перемещаются

38 голосов
/ 01 февраля 2009

Вот пример:

<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>

Вы захотите исследовать замыкания и узнать, как их использовать для создания закрытых членов .

31 голосов
/ 15 мая 2012

Ключ, насколько я понимаю, заключается в том, что Javascript имеет определение уровня функции по сравнению с более распространенной областью видимости блока C.

Вот хорошая статья на эту тему.

26 голосов
/ 06 апреля 2010

В «Javascript 1.7» (расширение Mozilla до Javascript) можно также объявлять переменные области блока с помощью оператора let :

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4
23 голосов
/ 14 сентября 2015

Идея определения объема в JavaScript, изначально разработанная Бренданом Айхом , возникла из HyperCard языка сценариев HyperTalk .

На этом языке показы были сделаны аналогично стопке карточек. Там была мастер-карта, называемая фоном. Это было прозрачно и может быть замечено как нижняя карта. Любой контент на этой базовой карте был предоставлен картам, размещенным поверх него. Каждая карта, помещенная сверху, имела свой собственный контент, который имел приоритет над предыдущей картой, но при желании имел доступ к предыдущим картам.

Именно так и разработана система определения объема JavaScript. У него просто разные имена. Карты в JavaScript известны как Контексты исполнения ECMA . Каждый из этих контекстов состоит из трех основных частей. Переменная среда, лексическая среда и привязка this. Возвращаясь к справочнику по картам, лексическая среда содержит весь контент из предыдущих карт ниже в стопке. Текущий контекст находится на вершине стека, и любой объявленный там контент будет храниться в переменной среде. Переменная окружение будет иметь приоритет в случае именования коллизий.

Эта привязка будет указывать на содержащий объект. Иногда контексты или контексты выполнения изменяются без изменения содержащего объекта, например, в объявленной функции, где содержащийся объект может быть window или функцией-конструктором.

Эти контексты выполнения создаются каждый раз, когда передается управление. Управление передается, когда код начинает выполняться, и это в основном делается из выполнения функции.

Так что это техническое объяснение. На практике важно помнить, что в JavaScript

  • Области являются технически "контекстами исполнения"
  • Контексты образуют стек сред, в которых хранятся переменные
  • Верх стека имеет приоритет (нижний - глобальный контекст)
  • Каждая функция создает контекст выполнения (но не всегда новый, это связывание)

Применяя это к одному из предыдущих примеров (5. «Закрытие») на этой странице, можно следовать стеку контекстов выполнения. В этом примере в стеке три контекста. Они определяются внешним контекстом, контекстом в немедленно вызываемой функции, вызываемой var шестым, и контекстом в возвращаемой функции внутри немедленно вызываемой функции var шестого.

i ) Внешний контекст. Имеет переменную среду a = 1
ii ) Контекст IIFE имеет лексическое окружение a = 1, но переменное окружение a = 6, которое имеет приоритет в стеке
iii ) Возвращенный контекст функции имеет лексическое окружение a = 6, и это значение, на которое ссылается оповещение при вызове.

enter image description here

17 голосов
/ 25 октября 2013

1) Существует глобальная область действия, область действия функции, а также области действия with и catch. В общем случае для переменных нет области уровня «блок» - операторы with и catch добавляют имена в свои блоки.

2) Области охватываются функциями вплоть до глобальной области видимости.

3) Свойства определяются путем прохождения цепочки прототипов. Оператор with переводит имена свойств объекта в лексическую область, определенную блоком with.

РЕДАКТИРОВАТЬ: ECMAAScript 6 (Harmony) предназначен для поддержки let, и я знаю, что Chrome позволяет флаг 'harmony', поэтому, возможно, он поддерживает его ..

Пусть будет поддержкой для определения уровня блока, но вы должны использовать ключевое слово, чтобы это произошло.

РЕДАКТИРОВАТЬ: Исходя из того, что Бенджамин указал на высказывания with и catch в комментариях, я отредактировал пост и добавил еще. Оба оператора with и catch вводят переменные в их соответствующие блоки, и является областью действия блока. Эти переменные связываются со свойствами переданных в них объектов.

 //chrome (v8)

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

РЕДАКТИРОВАТЬ: Уточняющий пример:

test1 ограничен блоком with, но имеет псевдоним a.test1. «Var test1» создает новую переменную test1 в верхнем лексическом контексте (функция или глобальная переменная), если только она не является свойством a.

Хлоп! Будьте осторожны, используя 'with' - точно так же, как var - noop, если переменная уже определена в функции, это также noop относительно имен, импортируемых из объекта! Небольшое упоминание имени, которое уже определено, сделает это намного безопаснее. Я лично никогда не буду использовать с этим.

10 голосов
/ 21 марта 2013

Я обнаружил, что у многих новичков в JavaScript возникают проблемы с пониманием того, что наследование доступно по умолчанию в языке, и что до сих пор это единственная область действия функции. Я предоставил расширение для beautifier, который я написал в конце прошлого года, под названием JSPretty. Функциональные цвета функционируют в коде области видимости и всегда связывают цвет со всеми переменными, объявленными в этой области. Закрытие визуально демонстрируется, когда переменная с цветом из одной области видимости используется в другой области видимости.

Попробуйте функцию по адресу:

См. Демо-версию по адресу:

Посмотреть код по адресу:

В настоящее время эта функция поддерживает 16 вложенных функций, но в настоящее время не окрашивает глобальные переменные.

...