为啥 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
。这样做的主要动机是,delete
和typeof
等运算符可以应用于带括号的表达式。
NB 我写这个答案的重点是提供一个简单易懂的解释,将技术术语保持在最低限度,同时仍然足够清晰,并提供所要求的 ECMAScript 标准参考,我希望对难以理解 typeof
运算符的开发人员提供有用的资源。
¹ 使用术语“变量”是为了便于理解。更正确的术语是标识符,它不仅可以通过变量声明引入Lexical Environment,还可以通过函数声明、形参、调用函数(arguments
)、with
/catch
块,将属性分配给全局对象,let
和 const
语句 (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 有时会被抛出,我不知道为啥
为啥 PasteSpecial 方法有时会抛出错误 1004?
为啥我的比较方法有时会抛出 IllegalArgumentException?
为啥我的线程池有时会抛出 `std::bad_function_call` 或 `double free or corruption (!prev)`