2013-03-13 15 views
6

Я понимаю проблемы с глобальными переменными масштаба и javascript и их общей нежелательностью; и что вы найдете их повсюду. Ниже (в браузере) эквивалентно:Javascript Global Scope Assignment

var foo = 3; // foo === 3, window.foo === 3 
bazz = 10; // bazz === 10, window.bazz === 10 

Объявления переменного с варом ключевым словом в глобальном масштабе является таким же, как объявить его без вара в коде: переменный присваиваются корень (window).

Один метод, который я вижу много (например, настройка Google Analytics) заключается в следующем:

var _gaq = _gaq || []; 

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

То, что я не понимаю, почему это выдает ошибку:

_gaq = _gaq || []; 

Они смотрят эквивалент мне: _gaq следует принимать значение _gaq или быть инициализирован как массив. Но он выдает контрольную ошибку - мой вопрос: почему они разные?

+1

Они не совсем эквивалентны, они отличаются поведением, когда вы пытаетесь ['delete'] (http://perfectionkills.com/understanding-delete/) их. – Bergi

+0

Лучшее объяснение «необъявленных назначений» и почему они отличаются от глобальных назначений ('foo = 0'! =' Var foo = 0' в глобальной области), я могу найти ** [в этом блоге] (http : //perfectionkills.com/understanding-delete/) **, который помог мне понять внутреннюю разницу. –

ответ

6

Вы никогда не можете читать переменные, которые не были объявлены, и это то, что вы пытаетесь сделать с выражением _gaq || [] в последнем случае.

В этом случае

_gaq = _gaq || []; 

_qaq не был объявлен до и, когда правая часть (_gaq || []) вычисляется, он выдает ошибку.

Вот шаг за шагом объяснение того, что происходит в этом случае:

Оператор присваивания описана в section 11.13.1 спецификации:

The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:

1. Let lref be the result of evaluating LeftHandSideExpression .
2. Let rref be the result of evaluating AssignmentExpression .
...

LeftHandSideExpression является _gaq, то AssignmentExpression является _gqa || [].

Итак, сначала оценивается _qaq, что приводит к unresolvable reference, так как переменная _gaq не объявлена. Эта оценка не ошибка.

Затем оценивается _gqa || []. Это LogicalORExpression и описан в section 11.11 как LogicalORExpression || LogicalANDExpression.В данном случае LogicalORExpression, с левой стороны, находится на _gaq и LogicalANDExpression, с правой стороны, находится на [].
Выражение вычисляется следующим образом:

1. Let lref be the result of evaluating LogicalORExpression .
2. Let lval be GetValue(lref) .
...

Мы уже знаем, что lref будет неразрешимой ссылка, потому что _gaq не был объявлен. Так что давайте посмотрим, что GetValue делает (определенный в section 8.7.1, V это значение передается GetValue):

1. If Type(V) is not Reference , return V .
2. Let base be the result of calling GetBase(V) .
3. If IsUnresolvableReference(V) , throw a ReferenceError exception.
...

Как вы можете видеть, ошибка ReferenceError брошен в третьем шаге этой процедуры, которые в turn получает исполнение, оценивая правую часть задания, и здесь возникает ошибка.

Итак, почему это не происходит с var _gaq = _gaq || [];?

Эта линия:

var _gaq = _gaq || []; 

фактически

var _gaq; 
_gaq = _gaq || []; 

из-за чего-то под названием hoisting [MDN]. Это означает, что когда оценивается _gaq, оно будет не результат неразрешимой ссылкой, но ссылка со значением undefined.

(Если переменная _gaq уже объявлена ​​(и, возможно, имеет значение), то var _gaq не будет иметь никакого эффекта.)


Если вы хотите создать _gaq глобально внутри функции, сделать он явно со ссылкой на window:

window._gaq = window._gaq || []; 
+0

Это выглядит правильно! «Вы никогда не сможете читать переменные, которые не были объявлены», я не согласен с этим утверждением. 'window.x', например, читает отлично и возвращает примитивный тип значения' undefined', если переменная 'x' не была объявлена ​​в' window' (при условии, что мы не определили 'window.x') –

+0

@Benjamin: Но в этот случай, 'window.x' является свойством объекта' window', а не переменной. И хотя глобальные переменные являются свойствами глобального объекта, вы не обращаетесь к объекту 'window', когда вы ссылаетесь на глобальную переменную« неявно », свойства« окна »просто помещаются в цепочку видимости функций. –

+0

Спасибо, что делает его намного яснее. Однако он предлагает вопрос: что такое «магия», когда вы просто пишете «foo = 10»? Если движок javascript обнаруживает или создает неявный 'var foo;' заявление, то почему он не делает это для моего примера? –

5

Если _gaq справа от = не был объявлен ранее с var, он выдаст контрольную ошибку. Вы пытаетесь ссылаться на переменную, которая не существует. «Магия» работает только для , назначая несуществующим переменным.

Это как сказать x = y + 1; проблема заключается не в несуществующих x, а в несуществующих y.

+0

Спасибо. Поэтому я понимаю, что движок пытается выработать значение переменной и ошибок, потому что он не был объявлен. В этом есть смысл. Там, где я ошибался, это просто отказ от «var» для globals не является сокращенным/необязательным способом их объявления, хотя это выглядит так, как это выглядит в основном. –

+1

@Part: Нет, опуская 'var' в порядке, как сокращенное. Проблема заключается не в записи необъявленной переменной, а в ее чтении. Так же, как в примере в этом ответе 'x = y + 1'. 'x' станет глобальным, если объявлен' y', но поскольку он не будет вызывать ошибку, которая 'y' не объявлена. Сначала оценивается правая часть, поэтому в случае «x = x || 42; 'вы пытаетесь прочитать' x' до его создания. –

+0

См. Выше, но я думаю, что очень важно понять, что подъем происходит только тогда, когда вы пишете var _even в глобальной области видимости_, ​​и, следовательно, 'var x = x || 0' работает, но' x = x || 0' doesn ' т. Вы могли бы сказать «хорошо, очевидно», но если вы не «коротко замыкаетесь» таким образом, то обнаруживается, что глобальный var является просто крутым. Отсюда моя первоначальная путаница. Спасибо за ответы. –

1

Это выдаст ошибку, потому что переменная не найдена в контексте цепи тока Exec контекста. Доступ к переменной, которая не может быть разрешена, приведет к ошибке.

_gaq = _gaq || []; 

Это, с другой стороны, будет пытаться решить _gac пытается искать его в качестве члена оконного объекта, которым оказывается глобальный контекст «владелец» объекта. Разница в этом случае заключается в том, что он не будет вызывать ошибку, но window._gaq будет возвращать неопределенные, потому что свойство не найдено в объекте окна.

_gaq = window._gaq || []; 

Таким образом, так как глобальный объект контекста этого окна (когда речь идет о браузерах), если _gaq определен, эти два заявления будут иметь тот же эффект. Разница будет замечена, если _gaq не определен, и доступ к нему с использованием объекта окна может иметь то преимущество, что не получил ошибку.

1

Основой концепции является hoisting, и это часто бывает сложно на практике. Переменные определяются в верхней части области действия, тогда как назначение все еще происходит там, где оно определено.

При этом var _gaq = _gaq фактически определена переменная до выполняется фактическая строка кода для назначения. Это означает, что при выполнении назначения переменная уже находится в области окна. Без var перед _gaq подъем не происходит, и поэтому _gaq еще не существует, когда выполняется задание, вызывающее ошибку ссылки.

Если вы хотите увидеть это в действии, вы можете проверить, если переменная _gaq добавляется в объект окна со следующим:

function printIsPropOnWindow(propToCheck) 
{ 
    for (prop in window) 
    { 
     if (prop == propToCheck) 
     { 
      console.warn('YES, prop ' + prop + ' was on the window object'); 
      return; 
     } 
    } 
    console.warn('NO, prop ' + propToCheck + ' was NOT on the window object'); 
} 


try { 
    var _gaq = function() { 
     printIsPropOnWindow("_gaq"); 
     return a; 
    }(); 
} catch (ex) { 
    printIsPropOnWindow("_gaq"); 
} 
_gaq = "1"; 
printIsPropOnWindow("_gaq"); 

Если вы попытаетесь это сделать один раз, как это и один раз вар перед _gaq вы увидите очень разные результаты, потому что у вас есть _gaq, а другой нет.

+0

Спасибо. Я думаю, что мое фундаментальное недоразумение заключается в том, что запись 'foo = true' точно такая же, как и запись« var foo = true », и что в первом случае« var »был неявным. Справедливости ради, я думаю, что это довольно распространенное заблуждение, потому что для большинства целей они одинаковы. Как отмечает Берги, первое становится собственностью окна ... и второе. Кроме того, это не так: удаление этого не удастся. В некотором тонком смысле они разные. –

+1

@Party: * разница * между 'var foo = ...' и 'foo = ...' в глобальной области не имеет ничего общего с вашей проблемой. Только тот факт, что объявления переменных поднимаются. –

+0

@Party Ark: Феликс Клинг прав. Подъемная установка изменяется *, когда * foo добавляется к объекту окна. И ваша проблема заключается в том, что вы пытаетесь назначить эту переменную до ее добавления в область окна, чтобы строка завершилась с ошибкой. Если у вас есть var, то он был вставлен->, что означает, что он добавляется к объекту окна в самом начале любой области, в которой вы находитесь. – purgatory101