5

我了解全局范围和 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 的值或者被初始化为一个数组。但它会引发参考错误 - 我的问题是:为什么它们不同?

4

4 回答 4

5

您永远无法读取尚未声明的变量,这就是您_gaq || []在最后一种情况下尝试使用的表达式。

在这种情况下

 _gaq = _gaq || [];

_qaq_gaq || []之前没有声明过,当评估右侧 ( ) 时,它会抛出错误。

以下是对这种情况下发生的事情的逐步解释:

赋值运算符在规范的第 11.13.1 节中描述:

生产AssignmentExpression : LeftHandSideExpression = AssignmentExpression评估如下:

1. 让lref是评估的结果LeftHandSideExpression
2. 让rref是评估的结果AssignmentExpression
...

LeftHandSideExpression_gaqAssignmentExpression_gqa || []

所以 first_qaq被评估,这导致一个不可解析的引用,因为变量_gaq没有被声明。此评估不会引发错误。

然后_gqa || []进行评估。这是 aLogicalORExpression并且在第 11.11 节中描述为LogicalORExpression || LogicalANDExpression。在这种情况下,LogicalORExpression,左侧,是_gaq并且LogicalANDExpression,右侧,是[]
表达式计算如下:

1. 让lref是评估的结果LogicalORExpression
2.lvalGetValue(lref)
...

我们已经知道这lref将是一个无法解决的引用,因为_gaq没有声明。所以让我们看看GetValue在做什么(在第 8.7.1 节中定义,V是传递给的值GetValue):

1.如果Type(V)不是Reference,返回V
2. 让base成为调用的结果GetBase(V)
3. 如果IsUnresolvableReference(V),则抛出ReferenceError异常。
...

如您所见,ReferenceError在此过程的第三步中引发了错误,该错误又通过评估赋值的右侧来执行,这就是引发错误的地方。

那么,为什么这不会发生var _gaq = _gaq || [];呢?

这一行:

var _gaq = _gaq || [];

实际上是

var _gaq;
_gaq = _gaq || [];

因为一个叫做提升[MDN]的东西。这意味着当_gaq被评估时,它不会导致无法解析的引用,而是带有 value 的引用undefined

(如果变量_gaq已经声明(并且可能具有值),则var _gaq不会产生任何影响。)


如果您想_gaq从函数内部全局创建,请通过引用显式window执行:

window._gaq = window._gaq || [];
于 2013-03-13T15:03:37.610 回答
4

如果_gaq右边的=之前没有用 声明过var,它会抛出一个引用错误。您试图引用一个不存在的变量。“魔术”仅适用于分配给不存在的变量。

这就像说x = y + 1; 问题不是不存在x,而是不存在y

于 2013-03-13T14:55:54.643 回答
1

这将引发错误,因为在当前执行上下文的上下文链中找不到该变量。访问无法解析的变量将导致错误。

_gaq = _gaq || [];

另一方面,这将尝试解析 _gac 尝试将其作为窗口对象的成员来查找,结果是全局上下文“持有人”对象。这种情况的不同之处在于它不会抛出错误,但是window._gaq会返回 undefined,因为在 window 对象中找不到该属性。

_gaq = window._gaq || [];

因此,由于全局上下文对象是窗口(当谈到浏览器时),如果定义了 _gaq,这两个语句将具有相同的效果。当 _gaq 未定义时会注意到差异,并且使用窗口对象访问它可以具有不会出错的优点。

于 2013-03-13T15:09:42.683 回答
1

这里的基本概念是提升,在实践中通常很棘手。变量定义在函数作用域的顶部,而赋值仍然发生在定义它的地方。

这样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 而另一个没有提升。

于 2013-03-13T15:37:07.430 回答