精读ECMAScript规范:全局对象

阙辰龙
2023-12-01

今天看到一个很有意思的问题:setTimeout是不是全局函数?

这个问题,我第一反应是“是”,因为setTimeout是挂在window对象的一个方法(也就是window.setTimeout),而window是浏览器环境下的全局对象,所以setTimeout显然是全局函数。

但是也有人说不是,因为方法(methods)和函数(functions)是两码事,全局函数应该是不通过任何对象即可调用的,而setTimeout是隐式的window.setTimeout调用。从这个角度来说,setTimeout应该是对象的方法,只是碰巧挂在了全局对象底下,所以看起来像是全局函数;并且列出了所谓全局函数的表格:

函数描述
decodeURI()解码某个编码的 URI。
decodeURIComponent()解码一个编码的 URI 组件。
encodeURI()把字符串编码为 URI。
encodeURIComponent()把字符串编码为 URI 组件。
escape()对字符串进行编码。
eval()计算 JavaScript 字符串,并把它作为脚本代码来执行。
isFinite()检查某个值是否为有穷大的数。
isNaN()检查某个值是否是数字。
Number()把对象的值转换为数字。
parseFloat()解析一个字符串并返回一个浮点数。
parseInt()解析一个字符串并返回一个整数。
String()把对象的值转换为字符串。
unescape()对由 escape() 编码的字符串进行解码。

想了一下,觉得人家说得有道理。

其实这是一个抠字眼的问题,全局函数到底是全局作用域里的函数,还是仅仅指全局对象里的函数,似乎并不太明确。如果不严格地说,全局作用域里的函数都可以算是全局函数,这其实也更符合我们平时使用时的习惯。不过,如果从狭义上说,可能在全局作用域中预定义的函数才是全局函数。我们不妨看看规范里关于狭义上的全局函数的载体,全局对象(global object),的相关内容是咋写的(参见EMCAScript 2020规范第18节,有删改)。

全局对象的几个特点:

  • 在进入任意执行上下文之前创建

  • 无法构造

    • 在使用new和super时会触发构造。

    • 怎么证明这一点呢,比如这么一段代码:

      class Test {
        constructor() {
          super();
        }
      }
      new Test();
      

      这个时候就会报错,因为全局对象不能构造,所以不能使用super。

  • 不会成为对象的constructor

    • 即,不会成为调用new运算符构造新对象时指定的那个constructor
  • 无法调用(毕竟不是函数)

  • 取决于实现,有一个[[Prototype]]内部方法

    • 没有真正意义上的可访问的prototype属性,也就是没有window.prototype

    • 但是可以通过非标准的__proto__属性访问。举例来说:

      • Chrome 85:

        Window {TEMPORARY: 0, PERSISTENT: 1, Symbol(Symbol.toStringTag): "Window", constructor: ƒ}
        
      • Firefox 80:

        WindowPrototype
          constructor: function ()
          <prototype>: WindowProperties { … }
        
      • Edge 44:

        [object WindowPrototype]: {constructor: function Window() { [native code] }}
        
      • ……

  • 可能会包括一些非规范方法

然后我们再来看看全局对象的一些属性:

  • globalThis
  • Infinity
  • NaN
  • undefined
  • Atomics
  • JSON
  • Math
  • Reflect

以及全局对象的一些方法,主要还是分成这几类:

  • 类型判断

    • isFinite
    • isNaN
  • 类型转换

    • eval
    • parseInt
    • parseFloat

    稍微解释一下,这里的类型转换不包括通过构造函数进行的转换。同时,从某种意义上来说,eval也是对字符串形式的代码进行了转换(并且执行)。

  • 构造函数

    • Array
    • ArrayBuffer
    • BigInt
    • BigInt64Array
    • BigUint64Array
    • Boolean
    • DataView
    • Date
    • Error
    • EvalError
    • Float32Array
    • Float64Array
    • Function
    • Int8Array
    • Int16Array
    • Int32Array
    • Map
    • Number
    • Object
    • Promise
    • Proxy
    • RangeError
    • ReferenceError
    • RegExp
    • Set
    • SharedArrayBuffer
    • String
    • Symbol
    • SyntaxError
    • TypeError
    • Uint8Array
    • Uint8ClampedArray
    • Uint16Array
    • Uint32Array
    • URIError
    • WeakMap
    • WeakSet

    在写这篇文章之前,我也不知道JS居然有这么多类型……

  • URI处理

    • decodeURI
    • decodeURIComponent
    • encodeURI
    • encodeURIComponent
    • escape
    • unescape

事实上,遵守EMCAScript规范的并不止JavaScript一个,比较著名的就是ActionScript,用来写flash的一门面向对象语言,我们也可以从那里找到佐证。比如,在ActionScript中,规范上提到的全局函数都是在全局实现的,而setTimeout则是在flash.utils这个包里;从这一点中就可以看出setTimeout并不是真正意义上的全局函数了。

此外,ActionScript中还自行实现了一些非规范的全局函数。这也证明了规范所言非虚,“可能会包括一些非规范方法”。

 类似资料: