单例内置对象 Global
ECMA-262 对内置对象的定义是“任何由 ECMAScript 实现提供、与宿主环境无关,并在 ECMAScript 程序开始执行时就存在的对象”。这就意味着,开发者不用显式地实例化内置对象,因为它们已经实例化好了。
当代码开始执行时,全局上下文中会存在两个内置对象:Global 和 Math。其中,Global 对象在大多数 ECMAScript 实现中无法直接访问。不过,浏览器将其实现为 window 对象。所有全局变量和函数都是 Global 对象的属性。
Global 对象是 ECMAScript 中最特别的对象,因为代码不会显式地访问它。ECMA-262 规定 Global 对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成 Global 对象的属性 。包括函数, 也包括 isNaN()、isFinite()、parseInt() 和 parseFloat(),实际上都是 Global 对象的方法。除了这些,Global 对象上还有另外一些方法。
URL 编码方法
encodeURI() 和 encodeURIComponent() 方法用于编码统一资源标识符(URI),以便传给浏览器。 有效的 URI 不能包含某些字符,比如空格。使用 URI 编码方法来编码 URI 可以让浏览器能够理解它们, 同时又以特殊的 UTF-8 编码替换掉所有无效字符。 ecnodeURI() 方法用于对整个 URI 进行编码,比如"www.wrox.com/illegal value.js"。而 encodeURIComponent() 方法用于编码 URI 中单独的组件,比如前面 URL 中的"illegal value.js"。这两个方法的主要区别是,encodeURI() 不会编码属于 URL 组件的特殊字符,比如冒号、斜杠、问号、 井号,而 encodeURIComponent() 会编码它发现的所有非标准字符。来看下面的例子:
let uri = "http://www.wrox.com/illegal value.js#start";
// "http://www.wrox.com/illegal%20value.js#start" ·
console.log(encodeURI(uri));
// "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start"
console.log(encodeURIComponent(uri));
这里使用 encodeURI() 编码后,除空格被替换为%20 之外,没有任何变化。而 encodeURIComponent() 方法将所有非字母字符都替换成了相应的编码形式。这就是使用 encodeURI() 编码整个 URI,但只使用 encodeURIComponent() 编码那些会追加到已有 URI 后面的字符串的原因。
注意:一般来说,使用 encodeURIComponent() 应该比使用 encodeURI() 的频率更高, 这是因为编码查询字符串参数比编码基准 URI 的次数更多。
与 encodeURI() 和 encodeURIComponent() 相对的是 decodeURI() 和 decodeURIComponent()。 decodeURI() 只对使用 encodeURI() 编码过的字符解码。例如,%20 会被替换为空格,但%23 不会被替换为井号(#),因为井号不是由 encodeURI() 替换的。类似地,decodeURIComponent() 解码所有被 encodeURIComponent() 编码的字符,基本上就是解码所有特殊值。来看下面的例子:
let uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start";
// http%3A%2F%2Fwww.wrox.com%2Fillegal value.js%23start
console.log(decodeURI(uri));
// http:// www.wrox.com/illegal value.js#start
console.log(decodeURIComponent(uri));
这里,uri 变量中包含一个使用 encodeURIComponent() 编码过的字符串。首先输出的是使用 decodeURI()解码的结果,可以看到只用空格替换了%20。然后是使用 decodeURIComponent() 解码的结果,其中替换了所有特殊字符,并输出了没有包含任何转义的字符串。(这个字符串不是有效的 URL。)
注意:URI方法 encodeURI()、encodeURIComponent()、decodeURI() 和 decodeURIComponent() 取代了 escape() 和 unescape() 方法,后者在 ECMA-262 第 3 版中就已经废弃了。URI 方法始终是首选方法,因为它们对所有 Unicode 字符进行编码,而原来的方法只能正确编码 ASCII 字符。不要在生产环境中使用 escape() 和 unescape()。
eval() 方法
eval() 方法可能是整个 ECMAScript 语言中最强大的了 。这个方法就是一个完整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。来看一个例子:
eval("console.log('hi')");
// 上面这行代码的功能与下一行等价:
console.log("hi");
当解释器发现 eval() 调用时,会将参数解释为实际的 ECMAScript 语句,然后将其插入到该位置。 通过 eval() 执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在 eval()调用内部被引用,比如下面这个例子:
let msg = "hello world";
eval("console.log(msg)"); // "hello world"
这里,变量 msg 是在 eval() 调用的外部上下文中定义的,而 console.log() 显示了文本"hello world"。这是因为第二行代码会被替换成一行真正的函数调用代码。类似地,可以在 eval() 内部定义一个函数或变量,然后在外部代码中引用,如下所示:
eval("function sayHi() { console.log('hi'); }");
sayHi();
这里,函数 sayHi() 是在 eval() 内部定义的。因为该调用会被替换为真正的函数定义,所以才可能在下一行代码中调用 sayHi()。对于变量也是一样的:
eval("let msg = 'hello world';");
console.log(msg); // Reference Error: msg is not defined
通过 eval() 定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在 eval() 执行的时候才会被创建。 **在严格模式下,在 eval() 内部创建的变量和函数无法被外部访问。**换句话说,最后两个例子会报错。同样,在严格模式下,赋值给 eval 也会导致错误:
"use strict";
eval = "hi"; // 导致错误
注意:解释代码字符串的能力是非常强大的,但也非常危险。在使用 eval() 的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对 XSS 利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。
Global 对象属性
Global 对象有很多属性,像 undefined、NaN 和 Infinity 等特殊值都是 Global 对象的属性。此外,所有原生引用类型构造函数,比如 Object 和 Function,也都是 Global 对象的属性。下表列出了所有这些属性。
属性 | 说明 |
---|---|
undefined | 特殊值undefined |
NaN | 特殊值 NaN |
Infinity | 特殊值 Infinity |
Object | Object 的构造函数 |
Array | Array 的构造函数 |
Function | Function 的构造函数 |
Boolean | Boolean 的构造函数 |
String | String 的构造函数 |
Number | Number 的构造函数 |
Date | Date 的构造函数 |
RegExp | RegExp 的构造函数 |
Symbol | Symbol 的伪构造函数 |
Error | Error 的构造函数 |
EvalError | EvalError 的构造函数 |
RangeError | RangeError 的构造函数 |
ReferenceError | ReferenceError 的构造函数 |
SyntaxError | SyntaxError 的构造函数 |
TypeError | TypeError 的构造函数 |
URIError | URIError 的构造函数 |
window 对象
虽然 ECMA-262 没有规定直接访问 Global 对象的方式,但浏览器将 window 对象实现为 Global 对象的代理。因此,所有全局作用域中声明的变量和函数都变成了 window 的属性。来看下面的例子:
var color = "red";
function sayColor() {
console.log(window.color);
}
window.sayColor(); // "red"
这里定义了一个名为 color 的全局变量和一个名为 sayColor() 的全局函数。在sayColor() 内部, 通过 window.color 访问了 color 变量,说明全局变量变成了 window 的属性。接着,又通过 window 对象直接调用了 window.sayColor() 函数,从而输出字符串。
注意:window 对象在 JavaScript 中远不止实现了 ECMAScript 的 Global 对象那么简单。
另一种获取 Global 对象的方式是使用如下的代码:
let global = function() {
return this;
}();
这段代码创建一个立即调用的函数表达式,返回了 this 的值。如前所述,当一个函数在没有明确 (通过成为某个对象的方法,或者通过 call()/apply())指定 this 值的情况下执行时,this 值等于 Global 对象。因此,调用一个简单返回 this 的函数是在任何执行上下文中获取 Global 对象的通用方式。