我理解全局范围和javascript变量及其一般不受欢迎的问题;并且你到处找到它们。以下(在浏览器中)是等效的:
var foo = 3; // foo === 3, window.foo === 3
bazz = 10; // bazz === 10, window.bazz === 10
在全局范围内使用var关键字声明变量与在代码中的任何位置声明它没有var相同:您的变量被分配给根(窗口)对象。
我经常看到的一种技术(例如设置谷歌分析)是这样的:
var _gaq = _gaq || [];
...我遵循的理由是,如果_gaq已被声明使用,如果不是将其创建为数组。它允许粗心编码不覆盖已分配给全局变量_gaq的任何值。
我不明白为什么会抛出错误:
_gaq = _gaq || [];
它们看起来与我相同:_gaq应该取_gaq的值或者初始化为数组。但它引发了一个引用错误 - 我的问题是:它们为什么不同?
答案 0 :(得分:5)
您永远不能读取尚未声明的变量,这就是您在最后一种情况下使用表达式_gaq || []
尝试的内容。
在这种情况下
_gaq = _gaq || [];
_qaq
之前未被声明,当评估右侧(_gaq || []
)时,它会抛出错误。
以下是对这种情况下正在发生的事情的逐步说明:
赋值运算符在规范的section 11.13.1中描述:
生产
AssignmentExpression : LeftHandSideExpression = AssignmentExpression
的评估如下:1。让
lref
成为评估LeftHandSideExpression
的结果 2.让rref
成为评估AssignmentExpression
的结果 ......
LeftHandSideExpression
为_gaq
,AssignmentExpression
为_gqa || []
。
因此,首先评估_qaq
,这会产生unresolvable reference,因为未声明变量_gaq
。此评估不会抛出错误。
然后评估_gqa || []
。这是LogicalORExpression
,在section 11.11中描述为LogicalORExpression || LogicalANDExpression
。在这种情况下,LogicalORExpression
(左侧)为_gaq
,LogicalANDExpression
(右侧)为[]
。
表达式评估如下:
1。让
lref
成为评估LogicalORExpression
的结果 2.让lval
成为GetValue(lref)
...
我们已经知道lref
将是一个无法解决的参考,因为未声明_gaq
。因此,让我们看看GetValue
正在做什么(在section 8.7.1中定义,V
是传递给GetValue
的值):
1。如果
Type(V)
不是Reference
,请返回V
2.让base
成为调用GetBase(V)
的结果 3.如果IsUnresolvableReference(V)
,则抛出ReferenceError
例外 ...
正如您所看到的,在此过程的第三步中会引发ReferenceError
错误,而该错误又通过评估赋值的右侧来执行,这就是抛出错误的位置。< / p>
那么,为什么var _gaq = _gaq || [];
不会发生这种情况?
这一行:
var _gaq = _gaq || [];
实际上是
var _gaq;
_gaq = _gaq || [];
因为名为hoisting [MDN]的东西。这意味着在评估_gaq
时,不会导致无法解析的参考,而是带有值undefined
的参考。
(如果变量_gaq
已经声明(并且可能有值),则var _gaq
将不会产生任何影响。)
如果您想在函数内部全局创建_gaq
,请参阅window
window._gaq = window._gaq || [];
答案 1 :(得分:4)
如果_gaq
右侧的=
之前未使用var
声明,则会引发参考错误。您正在尝试引用不存在的变量。 “魔术”只适用于将分配给不存在的变量。
就像说x = y + 1
;问题不是不存在的x
,而是不存在的y
。
答案 2 :(得分:1)
这将引发错误,因为在当前执行上下文的上下文链中找不到该变量。访问无法解析的变量将导致错误。
_gaq = _gaq || [];
另一方面,这将尝试解决_gac试图将其作为窗口对象的成员进行查找,结果证明它是全局上下文“持有者”对象。这种情况的不同之处在于它不会抛出错误,但window._gaq
将返回undefined,因为在window对象中找不到该属性。
_gaq = window._gaq || [];
因此,由于全局上下文对象是窗口(当谈到浏览器时),如果定义了_gaq,这两个语句将具有相同的效果。当没有定义_gaq时会注意到差异,使用window对象访问它可以获得不会出错的优点。
答案 3 :(得分:1)
这里的基本概念是hoisting,在实践中通常很棘手。变量在函数范围的顶部定义,而赋值仍然发生在定义的位置。
使用此var _gaq = _gaq
变量实际上是在之前定义,执行赋值的实际代码行。这意味着当赋值发生时,变量已经在窗口范围内。如果没有_gaq前面的var,则不会发生提升,因此当赋值运行导致引用错误时,_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之前使用var一次,您会看到非常不同的结果,因为其中一个有_gaq悬挂而另一个没有。