今天看到一个很有意思的问题: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
无法调用(毕竟不是函数)
取决于实现,有一个[[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] }}
……
可能会包括一些非规范方法
然后我们再来看看全局对象的一些属性:
以及全局对象的一些方法,主要还是分成这几类:
类型判断
类型转换
稍微解释一下,这里的类型转换不包括通过构造函数进行的转换。同时,从某种意义上来说,eval也是对字符串形式的代码进行了转换(并且执行)。
构造函数
在写这篇文章之前,我也不知道JS居然有这么多类型……
URI处理
事实上,遵守EMCAScript规范的并不止JavaScript一个,比较著名的就是ActionScript,用来写flash的一门面向对象语言,我们也可以从那里找到佐证。比如,在ActionScript中,规范上提到的全局函数都是在全局实现的,而setTimeout
则是在flash.utils这个包里;从这一点中就可以看出setTimeout
并不是真正意义上的全局函数了。
此外,ActionScript中还自行实现了一些非规范的全局函数。这也证明了规范所言非虚,“可能会包括一些非规范方法”。