JavaScript 是否具有未定义的行为(类似于 C),或者它是否完全由规范定义且具有确定性?
请注意,我正在丢弃实现错误和规范分歧。我也在丢弃像Math.random()
and之类的东西Date.now()
。
是否有一段 JavaScript 代码的行为未完全由 JavaScript 规范确定,并且因此具有“未定义的行为”?
JavaScript 是否具有未定义的行为(类似于 C),或者它是否完全由规范定义且具有确定性?
请注意,我正在丢弃实现错误和规范分歧。我也在丢弃像Math.random()
and之类的东西Date.now()
。
是否有一段 JavaScript 代码的行为未完全由 JavaScript 规范确定,并且因此具有“未定义的行为”?
规范中有很多东西明确地留给了实现。尤其是当涉及到主机对象时,可能会有很多怪癖。与宿主对象无关的示例:
全局对象的 [[Prototype]] 和 [[Class]] 内部属性的值是implementation-dependent。
[如果有效数字太多] mathInt 可能是数学整数值的实现相关的近似值,它由 Z 以基数-R 表示法表示。
15.3.4.2 函数.prototype.toString
返回函数的依赖于实现的表示。
几乎所有的日期解析/字符串化算法都是依赖于实现的,这包括toLocaleString
、toString
和构造函数。parse
Date
15.4.4.11 Array.prototype.sort (comparefn) - 可能是最好的例子:
如果 comparefn 不是未定义的并且不是此数组元素的一致比较函数,则 sort 的行为是实现定义的。
[...] 如果 proto 不为 null 并且存在一个整数 j 使得满足以下所有条件,那么 sort 的行为是实现定义的:
- obj 是稀疏的 (15.4)
- 0 ≤ j < 长度
如果 obj 是稀疏的并且以下任何条件为真,则排序的行为也是实现定义的:
- obj 的 [[Extensible]] 内部属性为 false。
- 名称为小于 len 的非负整数的 obj 的任何数组索引属性都是 [[Configurable]] 属性为 false 的数据属性。
如果 obj 的名称为小于 len 的非负整数的任何数组索引属性是访问器属性或者是 [[Writable]] 属性为 false 的数据属性,则排序的行为也由实现定义。
最突出的是:
执行依赖于实现的调用序列 […]
15.5.4.9 String.prototype.localeCompare(那个)
两个字符串以实现定义的方式进行比较
15.5.4.11 String.prototype.replace [在替换符号中,如果数量大于组数],结果是实现定义的。
我将在此处停止列出,您可以通过规范搜索。其他值得注意的地方可能是toLocaleString
方法,或者方法返回的依赖于实现的近似值Math
。
我发现了几个例子,引用了 ECMAScript 语言规范(强调我的):
在某些实现中,外部代码可能能够检测各种非数字值之间的差异,但这种行为是依赖于实现的;对于 ECMAScript 代码,所有 NaN 值彼此无法区分。
如果使用多个参数调用 toFixed 方法,则行为未定义(参见第 15 节)。
如果使用多个参数调用 toExponential 方法,则行为未定义(参见第 15 节)。
如果使用多个参数调用 toPrecision 方法,则行为未定义(参见第 15 节)。
当使用少于两个参数调用 UTC 函数时,行为取决于实现。
我发现
Array.sort(compareFunction);
在 compareFunction 行为不正常的情况下(即为相同的输入返回一致的结果)。
从规范:
如果 comparefn 不是未定义的并且不是此数组元素的一致比较函数(见下文),则排序的行为是实现定义的。
任何调用 C 风格的未定义行为以响应任何输入的程序都不适合与不可信的输入一起使用。尽管在许多情况下 ECMAScript 规范没有指定精确的行为,但它并没有给实现与 C 编译器具有未定义行为相同的自由来否定时间定律和因果关系。
首先,定义非正式称为 JavaScript 的语言的 ECMA-262 标准使用术语“实现依赖”和“实现定义”,而没有定义这些术语的含义。实际上,所有这些行为都是未定义的;对于应该发生的事情没有给出任何要求。崩溃或行为不可预测的实现是符合要求的;当然,如果它记录了该行为:那么崩溃或不稳定的行为就是实现所定义的。相比之下,ISO C 标准正式定义了诸如“未指定行为”、“实现定义的行为”和“未定义的行为”之类的术语。这些词无论出现在哪里,实际上都意味着某种东西。
其次,ECMA-262 标准对实施限制保持沉默。这并不意味着他们不在那里。例如,给定特定实现中的 Javascript 程序可以有任何深度的递归吗?任意数量的函数参数?任何深度的词法作用域?它可以分配任意数量的对象吗?当然不是,对吧?“limit”这个词甚至没有出现在 ECMA-262 2018 的任何地方,除了作为函数参数名称。例如,该文档没有说 ECMAScript 的一致实现必须允许具有 64 个参数的函数。因此,实现必须支持带有任意数量参数的函数是有道理的。如果我们用一千万个参数做一个函数,然后实现崩溃,它就是不合格的;ECMA-262 没有任何地方声明允许这种故障。如果 C 编译器由于函数中的一千万个参数而崩溃,我们可以明确指出这是程序中的一个问题:它超过了标准中记录的最小实现限制,因此无法严格遵守(正式术语)。这是一种未定义行为的情况:它涉及一个不可移植的程序,标准对此没有任何要求(不需要实现来处理那么多函数参数)。