为什么typeof有时会抛出ReferenceError错误?

14
在 Chrome 和 Firefox 中,
typeof foo
< p > 评估结果为'undefined'

但是

typeof (function() { return foo; })()

抛出一个错误:

ReferenceError: foo is not defined

这打破了我对表达式可替代性的理解! 直到现在,我不知道有哪种情况下 foo(function() { return foo; })() 不相同。

这是标准行为吗?如果是,引用ECMAScript标准的相关部分会很有帮助。


编辑:

另一个例子:

typeof (foo)
typeof (foo + 0)

我本以为 (foo)(foo + 0) 都会报错。

但第一个没有错误,第二个有。


2
我不确定为什么 typeof foo 没有抛出 ReferenceError,但函数表达式可能是在 return 语句上抛出错误,而不是在 typeof 上。 - ajp15243
@ajp15243,正确。但就像typeof(1+1)首先评估1+1一样,我认为表达式foo也会被评估,例如评估为2'hello world',并且该评估也会引发ReferenceError。当然,事实并非如此。 - Paul Draper
1
您的匿名函数先被执行,抛出了一个异常。当您尝试评估任何其他未定义的表达式时,情况是相同的: typeof (1 + foobar) // => ReferenceError - ajm
2
@ajp15243我理解他的意思;如果你考虑语言的评估方式,你必须在评估typeof之前评估foo。也就是说,例如,如果你说alert("test"),表达式"test"在调用alert之前被评估,并且应该独立于alert进行评估,即alert("test")someOtherFunction("test")不应影响常量"test"的值。如果这是真的,为什么typeof不独立于上下文评估foo呢? - soktinpk
“这破坏了我对表达式可替换性的概念” - 对我来说,async () => await xasync () => () => await x 破坏了它。(或类似地 (function*(){ yield x; }) vs. (function*(){ () => yield x; })). - Sebastian Simon
显示剩余4条评论
5个回答

25
基本上,typeof 运算符用于检查变量¹ 是否未定义并返回 "undefined"。也就是说,在执行 GetValue 算法之前,typeof 为未声明的变量¹ 返回一个已定义的值。
引用 ECMAScript 5.1 § 11.4.3 The typeof Operator(重点在于“强调”):

11.4.3 typeof运算符

产生式 UnaryExpression : typeof UnaryExpression 的计算方式如下:

  1. UnaryExpression 计算的结果赋值给变量 val
  2. 如果 Type(val) 是 Reference,则执行以下步骤:

    2.1. 如果 IsUnresolvableReference(val) 为 true,则返回 "undefined"

    2.2 将 val 赋值为 GetValue(val)。

  3. 根据 Table 20 中的规定,返回由 Type(val) 决定的字符串。

另一方面,return语句 -- 像大多数读取标识符值的运算符和语句一样 -- 总是会调用GetValue,如果标识符未解析(未声明变量),则会抛出异常。引用 ECMAScript 5.1 § 8.7.1 GetValue (V)(已加粗):

8.7.1 GetValue (V)

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

现在,分析代码:

typeof (function() { return foo; })()

这段代码将实例化一个函数对象,执行它,然后才会使用typeof操作函数的返回值(函数调用比typeof运算符具有precedence优先级)。
因此,在评估IIFE的return语句时,代码会抛出异常,因为在typeof操作可以被评估之前就已经发生了异常。
一个类似但更简单的例子:
typeof (foo+1)

typeof 之前会先对加法进行求值。当 Addition Operatorfoo 上调用 GetValue 时,这将会抛出一个错误,在 typeof 开始起作用之前。

现在:

typeof (foo)

不像分组运算符(括号)实际上没有 "评估" 任何内容,它只是强制优先级而不会引发错误。更具体地说,分组运算符不会调用GetValue。在上面的例子中,它返回一个(无法解析的)引用注释 ES5.1规范甚至添加了一条注释:

注意:此算法不对评估Expression的结果应用GetValue。这样做的主要动机是为了可以将类似于deletetypeof的操作符应用于括号表达式。


N.B. 我写这篇答案的重点是提供简单易懂的解释,尽可能减少技术术语的使用同时仍然足够清晰,并提供所请求的ECMAScript标准参考,希望能成为那些难以理解typeof运算符的开发人员有用的资源。
¹ 为了便于理解,本文中使用“变量”一词。更正确的术语应该是“标识符”,可以通过变量声明、函数声明、形式参数、调用函数(arguments)、with/catch块、将属性赋值给全局对象、letconst语句(ES6)以及可能还有其他方式引入Lexical Environment中。

哦,太好了,我花了几分钟的时间写了一个答案,结果一个匿名的踩票者根本没有看就把它踩掉了。 - Fabrício Matté
Fabcricio,我不知道为什么你的回答被踩了。 :( 看起来很好。 - Paul Draper
2
@FabrícioMatté 嗯...在我看来,这可能是迄今为止最好的答案。 - soktinpk
@PaulDraper 谢谢,没问题。我已经为 @RobG 的回答点赞,因为他在我之前已经解释了大部分内容。 - Fabrício Matté

8

这是标准行为吗?

是的。 typeof 不会抛出错误,因为它只是返回一个值 如规定。然而,正如其他答案所说,当评估操作数时,代码会失败。

如果是这样,引用ECMAScript标准的相关部分将会很有帮助。

在评估函数表达式时,尝试解析 foo 的值(以便可以返回)将使用参数 foo 调用内部 GetValue 方法。然而,由于 foo 没有被声明或以其他方式创建,因此会抛出引用错误。

编辑

对于以下情况:

typeof (foo)

"(" 和 ")" 是 标点符号,表示分组,例如在调用像 foo(a, b) 这样的函数时,表示可能为空的参数列表,或者表示要计算的表达式,例如 if (x < 0) 等等。

typeof (foo) 的情况下,它们仅表示在应用 typeof 操作符之前评估 foo。因此,作为有效标识符的 foo,根据上面的链接传递给 typeof,它尝试解析它,无法解析,确定它是一个不可解析引用,并返回字符串"undefined"

在以下情况下:

typeof (foo + 0)

括号导致表达式 foo + 0 先被求值。在获取 foo 的值时,会抛出引用错误,因此 typeof 无法执行。请注意,没有括号的情况下:
typeof foo + 0 // undefined0

由于运算符优先级的原因:typeof foo返回字符串"undefined",因此+变成加法运算符,因为其中一个参数是字符串,它执行的是连接操作(加法的字符串版本,而不是数学版本),所以0被转换为字符串"0"并与"undefined"连接,结果是字符串"undefined0"
因此,任何时候都会尝试评估具有未解析引用的表达式(例如未声明或初始化的变量),都会抛出引用错误,例如:
typeof !foo 

由于为了确定要传递给 typeof 的内容,必须评估表达式,所以也会抛出引用错误。要应用 ! 运算符,必须获取 foo 的值,并在尝试时抛出引用错误。


这是否也解释了为什么 typeof (foo) 不会抛出错误? - Paul Draper
据我所知,分组运算符(括号)只强制优先级,它们并不“评估”任何内容。 - Fabrício Matté

2
浏览规范后,我认为这完全取决于所讨论的操作符何时尝试对其操作数运行GetValue()函数。 typeof首先试图确定其操作数的Type类型。如果该类型是一个Reference并且IsUnresolvableReference(),则放弃,并返回undefined 本质上,它不会完全评估操作数;如果这样做,任何为undefined的值都将引发异常,因此它会短路并返回一个好的、有用的字符串。
在这些示例中,自执行函数和加法操作符调用GetValue时,没有typeof一样先检查IsUnresolvableReference():如果引用未解析(在我们的情况下,fooundefined),则它们会调用GetValue并抛出异常。(我想!这是我从阅读规范中得出的最佳猜测。)

1
今天怎么有这么多人点踩呢?这非常准确。+1 - Fabrício Matté
1
请注意,undefined 有点模糊不清 -- 例如 var foo; 表示 foo 已声明但其原始值为 undefined。我认为 "未声明" 或 "无法解析" 更加明确表达您的意思。 - Fabrício Matté

2
"

"错误 "ReferenceError: foo未定义" 不是由 typeof 抛出的,而是由函数本身抛出的。如果您使用了:

"
typeof (function() { return 2; })()

如果按照预期,它应该返回"number",但在这个例子中,JavaScript甚至没有运行typeof的步骤。你收到的错误与运行以下内容时相同:

function test () {
    return foo;
}
test();

1
这是标准行为。 typeof 运算符几乎接受您传递给它的下一个变量的引用。
所以让我们尝试一下 typeof foo
JavaScript 解释器查看 typeof 并找到 foo 的类型。
现在我们再尝试一下 typeof (function() { return foo })() JavaScript 解释器查看 typeof。由于之后的表达式不是变量,因此它会评估该表达式。(function() { return foo })() 抛出 ReferenceError,因为 foo 未定义。如果有可能传递变量的引用,即类似于 (function() { return *foo })(),那么这种情况就不会发生。
注意:根据此,人们可能认为 typeof (foo) 会抛出错误,因为 (foo) 不是变量,必须进行求值,但这是不正确的;如果 foo 未定义,则 typeof (foo) 也将返回 "undefined"。
本质上,解释器在“安全”上下文中评估下一个变量(但不是表达式),以便 typeof 不会引发错误。

有点令人困惑。


我不明白为什么 typeof (foo) 不会抛出错误。 - Paul Draper
@PaulDraper 是的,正如我所说的那样,这很令人困惑。我认为可能是因为 (foo) 也被评估为变量,因此在“安全”的非错误上下文中运行。然而,普通的非变量表达式不会在该上下文中运行。 - soktinpk
那么,哪些是“安全”的,哪些不是? - Paul Draper
我相信 foo(foo) 是唯一“安全”的。即使是像 false || foo(0, foo) 这样的表达式也会抛出错误。(逗号运算符返回最后一个值) - soktinpk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接