为啥 typeof 有时只抛出 ReferenceError?

Posted

技术标签:

【中文标题】为啥 typeof 有时只抛出 ReferenceError?【英文标题】:Why does typeof only sometimes throw ReferenceError?为什么 typeof 有时只抛出 ReferenceError? 【发布时间】:2014-07-31 18:57:24 【问题描述】:

在 Chrome 和 Firefox 中,

typeof foo

评估为'undefined'

但是

typeof (function()  return foo; )()

抛出错误:

ReferenceError: foo is not defined

这破坏了我对表达式可替代性的概念!直到现在,我才知道foo(function() return foo; )()不一样的条件。

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


编辑:

另一个例子:

typeof (foo)
typeof (foo + 0)

我希望 (foo)(foo + 0) 会抛出错误。

但是第一个没有错误;第二个。

【问题讨论】:

我不确定为什么typeof foo 没有抛出ReferenceError,但函数表达式可能在return 语句上抛出错误,而不是在typeof 上。跨度> 同意。您正在尝试返回未定义的内容,并且您的代码在那里引发了异常。 (function() return typeof foo; )() 按您的想法工作。 @ajp15243,正确。但正如typeof (1 + 1) 首先评估1 + 1,我认为表达式foo 被评估,例如到2'hello world',并且该评估也会抛出ReferenceError。当然,事实并非如此。 你的匿名函数首先执行,抛出异常。当您尝试评估任何其他未定义的表达式时,也会发生同样的事情:typeof (1 + foobar) // => ReferenceError @ajp15243 我明白他在说什么;如果您考虑如何评估语言,则必须先评估foo,然后再评估typeof。也就是说,例如,如果您说alert("test"),则表达式"test" 在调用alert 之前进行评估,并且应该独立于警报进行评估,即alert("test")someOtherFunction("test") 不应影响常量"test"。如果这是真的,为什么typeof 不独立于上下文评估foo 【参考方案1】:

typeof 没有抛出错误“ReferenceError: foo is not defined”,而是由函数本身抛出的。如果您使用过:

typeof (function()  return 2; )()

它会按预期返回“数字”,但在这个例子中,javascript 甚至没有达到typeof 在任何东西上运行的程度。您收到与运行时相同的错误:

function test () 
    return foo;

test();

【讨论】:

【参考方案2】:

这是标准行为。 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) 没有抛出错误。 @PaulDraper 是的,正如我所说,这令人困惑。我相信这可能是因为 (foo) 也被评估为变量,因此它在“安全”非错误上下文中运行。但是,正常的非变量表达式不会在该上下文中运行。 那么,哪些是“安全的”,哪些不是? 我相信 foo(foo) 是唯一“安全”的。甚至像 false || foo(0, foo) 这样的表达式也会引发错误。 (逗号运算符返回最后一个值)【参考方案3】:

这是标准行为吗?

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

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

在评估函数表达式时,尝试解析 foo 的值(以便可以返回)将调用带有参数 foo 的内部 GetValue 方法.但是,由于 foo 尚未声明或以其他方式创建,因此会引发引用错误。

编辑

如果是:

typeof (foo)

"(" 和 ")" 是punctuators,表示一个分组,例如调用foo(a, b) 之类的函数时的(可能为空的)参数列表,或者要评估的表达式,例如if (x < 0) 等等。

typeof (foo) 的情况下,它们只是表示在应用 typeof 运算符之前评估 foo。所以 foo,作为一个有效的标识符,被传递给 typeof,每个上面的链接,它试图解析它,不能,确定它是一个 unresolveable 引用,并返回字符串"undefined"

如果是:

typeof (foo + 0)

括号导致表达式foo + 0首先被计算。当获取 foo 的值时,会抛出一个引用错误,因此 typeof 无法操作。请注意,没有括号:

typeof foo + 0 // undefined0

由于运算符优先级:typeof foo 返回字符串"undefined",所以+ 变为addition operator,因为其中一个参数是字符串,它进行连接(加法的字符串版本,而不是数学版本) ,所以0 被转换为字符串"0" 并连接到"undefined",结果是字符串"undefined0"

因此,任何时候尝试评估具有不可解析引用的表达式(例如,未声明或初始化的变量)都会引发引用错误,例如

typeof !foo 

也会抛出一个引用错误,因为为了计算出要传递给 typeof 的内容,必须计算表达式。要应用 ! 运算符,必​​须获取 foo 的值,并且在尝试该值时会引发引用错误。

【讨论】:

这是否也解释了为什么typeof (foo) 不抛出错误? @PaulDraper AFAIK 分组运算符(括号)仅强制优先,它们不会“评估”任何内容。【参考方案4】:

基本上,typeof 运算符检查变量¹ 是否不可解析并返回 "undefined"。也就是说,typeof 在到达 GetValue 算法之前返回未声明变量的定义值¹,该算法抛出未声明的变量¹。

引用ECMAScript 5.1 § 11.4.3 The typeof Operator(强调):

11.4.3 typeof 运算符

产生式 UnaryExpression : typeof UnaryExpression 是 评价如下:

    val 为计算 UnaryExpression 的结果。

    如果Type(val) 是Reference,那么

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

    2.2 设 val 为 GetValue(val)。

    根据Table 20返回一个由Type(val)确定的String。

另一方面,return statement - 与大多数从标识符读取值的运算符和语句一样 - 将始终调用GetValue,这会引发无法解析的标识符(未声明的变量)。引用ECMAScript 5.1 § 8.7.1 GetValue (V)(强调):

8.7.1 获取值(V)

    如果Type(V) 不是引用,则返回V。 设 base 为调用 GetBase(V) 的结果。 如果IsUnresolvableReference(V),抛出ReferenceError异常。

现在,分析代码:

typeof (function()  return foo; )()

此代码将实例化一个函数对象并执行它,然后typeof 才会对函数的返回值进行操作(函数调用采用precedence 而不是typeof 运算符)。

因此,代码在评估 IIFE 的 return 语句时抛出,然后才能评估 typeof 操作。

一个类似但更简单的例子:

typeof (foo+1)

typeof 之前计算加法。当Addition Operator 在foo 上调用GetValue 时,这将引发错误,然后typeof 起作用。

现在:

typeof (foo)

不会抛出错误,因为grouping operator(括号)本身并不“评估”任何东西,它只是强制优先。更具体地说,分组运算符不调用GetValue。在上面的示例中,它返回一个(无法解析的)Reference

annotated ES5.1 spec 甚至为此添加了注释:

注意 此算法不适用于计算表达式的结果GetValue。这样做的主要动机是,deletetypeof 等运算符可以应用于带括号的表达式。


NB 我写这个答案的重点是提供一个简单易懂的解释,将技术术语保持在最低限度,同时仍然足够清晰,并提供所要求的 ECMAScript 标准参考,我希望对难以理解 typeof 运算符的开发人员提供有用的资源。

¹ 使用术语“变量”是为了便于理解。更正确的术语是标识符,它不仅可以通过变量声明引入Lexical Environment,还可以通过函数声明、形参、调用函数(arguments)、with /catch 块,将属性分配给全局对象,letconst 语句 (ES6),可能还有其他一些方式。

【讨论】:

哦,太好了,花我生命中的几分钟写一个答案,这样一个匿名投票者就可以不读就直接回答它。 Fabcricio,我不知道为什么您的回答被否决了。 :( 看起来很棒。 @FabrícioMatté 是的......这可能是迄今为止最好的答案,恕我直言 @PaulDraper 谢谢,没问题。我也赞成@RobG 的回答,因为我也解释了我面前的大部分内容。【参考方案5】:

深入研究规范,我认为这一切都归结为当有问题的运算符尝试在其操作数上运行GetValue()

typeof attempts to determine its operand's Type first. If that type is a Reference and is IsUnresolvableReference(), then it bails out and returns undefined.本质上,它并没有完全评估操作数;如果是这样,undefined 的任何内容都会引发异常,因此它会短路并返回一个漂亮、有用的字符串。

在示例中,自执行函数和加法运算符调用GetValue 无需首先检查IsUnresolvableReference(),就像typeof 一样:they call GetValue and throw an exception 如果引用未解决(@987654334 @ 在我们的例子中是 undefined)。 (我认为!这是我阅读规范后的最佳猜测。)

【讨论】:

今天投反对票的小仙女们怎么了?这是相当准确的。 +1 请注意,undefined 有点模棱两可——例如var foo; 表示 foo 已声明但具有原始值 undefined。我相信“未声明”或“无法解决”对于您的意思来说不那么模棱两可。

以上是关于为啥 typeof 有时只抛出 ReferenceError?的主要内容,如果未能解决你的问题,请参考以下文章

为啥有时会抛出 FileNotFoundException

FileNotFoundException 有时会被抛出,我不知道为啥

为啥 PasteSpecial 方法有时会抛出错误 1004?

为啥我的比较方法有时会抛出 IllegalArgumentException?

为啥我的线程池有时会抛出 `std::bad_function_call` 或 `double free or corruption (!prev)`

[自动化测试]Stale Element Reference Exception