ECMA-262 对内置对象的定义是:“由 ECMAScript 实现提供的、不依赖于宿主环境的对象,这些对象在 ECMAScript 程序执行之前就已经存在了。”意思就是说,不必显式地实例化内置对象,因为它们已经实例化了。常见的内置对象有 Object、Array 和 String 等。另外,ECMA-262 还定义了两个单体内置对象:Global 和 Math。
1.Global 对象
Global(全局)对象是 ECMAScript 中最特别的一个对象,因为不管从什么角度上看,这个对象都是不存在的。
ECMAScript 中的 Global 对象在某种意义上是作为一个终极“兜底儿对象”来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是 Global 对象的属性。诸如 isNaN()
、isFinite()
、parseInt()
以及 parseFloat()
,实际上全都是 Global 对象的方法。除此之外,Global 对象还包含其他一些方法。
1.1 URI 编码方法
Global 对象的 encodeURI()
和 encodeURIComponent()
方法可以对 URI(Uniform Resource Identifiers,通用资源标识符)进行编码,以便发送给浏览器。有效的 URI 中不能包含某些字符,例如空格。而这两个 URI 编码方法就可以对 URI 进行编码,它们用特殊的 UTF-8 编码替换所有无效的字符,从而让浏览器能够接受和理解。
其中,encodeURI()
主要用于整个 URI,而 encodeURIComponent()
主要用于对 URI 中某一段进行编码。它们的主要区别在于,encodeURI()
不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、问号和井字号;而 endoceURIComponent()
则会对它发现的任何非标准字符进行编码。看下面的例子:
1 | var uri = "http://www.wrox.com/illegal value.htm#start"; |
使用 encodeURI()
编码后的结果是除了空格之外的其他字符都原封不动,只有空格被替换成了 %20
。而 encodeURIComponent()
方法则会使用对应的编码替换所有非字母数字字符。这也正是可以对整个 URI 使用 encodeURI()
,而只能对附加在现有 URI 后面的字符串使用 encodeURIComponent()
的原因所在。
一般来说,使用 encodeURIComponent() 方法的时候要比使用 encodeURI() 更多,因为在实践中更常见的是对查询字符串参数而不是对基础 URI 进行编码。
与 encodeURI()
和 encodeURIComponent()
方法对应的两个方法分别是 decodeURI()
和 decodeURIComponent()
。其中,decodeURI()
只能对使用 encodeURI()
替换的字符进行编码。例如,它可将 %20
替换成一个空格,但不会对 %23
作任何处理,因为 %23
表示井字号(#
),而井字号不是使用 encodeURI()
替换的。同样地,decodeURIComponent()
能够解码使用 encodeURIComponent()
编码的所有字符,即它可以解码任何特殊字符的编码。
1 | var uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"; |
这里,变量 uri
包含着一个由 encodeURIComponent()
编码的字符串。在第一次调用 decodeURI()
输出的结果中,只有 %20
被替换成了空格。而在第二次调用 decodeURIComponent()
输出的结果中,所有特殊字符的编码都被替换成了原来的字符,得到了一个未经转义的字符串。
1.2 eval() 方法
eval()
方法就像是一个完整的 ECMAScript 解析器,它只接受一个参数,即要执行的 ECMAScript 字符串。看下面的例子:
1 | eval("alert('hi')"); |
这行代码的作用等价于下面这行代码:
1 | alert("hi"); |
当解析器发现代码中调用 eval()
方法时,它会将传入的参数当作实际的 ECMAScript 语句来解析,然后把执行结果插入到原位置。通过 eval()
执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链。这意味着通过 eval()
执行的代码可以引用在包含环境中定义的变量,举个例子:
1 | var msg = "hello world"; |
可见,变量 msg
是在 eval()
调用的环境之外定义的,但其中调用的 alert()
仍然能够显示 "hello world"
。这是因为上面第二行代码最终被替换成了一行真正的代码。同样地,也可以在 eval()
调用中定义一个函数,然后再在该调用的外部中引用这个函数:
1 | eval("function sayHi() { alert('hi'); }"); |
显然,函数 sayHi()
是在 eval()
内部定义的。但由于对 eval()
的调用最终会被替换成定义函数的实际代码,因此可以在下一行调用 sayHi()
。对于变量也一样:
1 | eval("var msg = 'hello world'; "); |
在 eval()
中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符串中,在严格模式下,为 eval 赋值也会导致错误:
1 |
|
能够解释代码字符串的能力非常强大,但也非常危险。因此在使用 eval() 时必须极为谨慎,特别是在用它执行用户输入数据的情况下。否则,可能会有恶意用户输入威胁你的站点或应用程序安全的代码(即所谓的代码注入)
1.3 Global 对象的属性
Global 对象还包含一些属性,例如,特殊的值 undefined、NaN 以及 Infinity 都是 Global 对象的属性。此外,所有原生引用类型的构造函数,像 Object 和 Function,也都是 Global 对象的属性。下表列出了 Global 对象 的所有属性。
属性 | 说明 |
---|---|
undefined | 特殊值 undefined |
NaN | 特殊值 NaN |
Infinity | 特殊值 Infinity |
Object | 构造函数 Object |
Array | 构造函数 Array |
Function | 构造函数 Function |
Boolean | 构造函数 Boolean |
String | 构造函数 String |
Number | 构造函数 Number |
Date | 构造函数 Date |
RegExp | 构造函数 RegExp |
Error | 构造函数 Error |
EvalError | 构造函数 EvalError |
RangeError | 构造函数 RangeError |
ReferenceError | 构造函数 ReferenceError |
SyntaxError | 构造函数 SyntaxError |
TypeError | 构造函数 TypeError |
URIError | 构造函数 URIError |
ES5 明确禁止给 undefined、NaN 和 Infinity 赋值,这样做即使在非严格模式下也会导致错误。
1.4 window 对象
ECMAScript 虽然没有指出如何直接访问 Global 对象,但 Web 浏览器都是将这个全局对象作为 window 对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,就都成为了 window 对象的属性。
1 | var color = "red"; |
这里定义了一个名为 color
的全局变量和一个名为 sayColor()
的全局函数。在 sayColor()
内部,我们通过 window.color
来访问 color
变量,以说明全局变量是 window 对象的属性。然后,又使用 window.sayColor()
来直接通过 window 对象调用这个函数,结果显示在了警告框。
另一种取得 Global 对象的方法是使用以下代码:
1 | var golbal = function() { |
以上代码创建了一个立即调用的函数表达式,返回 this
值。在没有给函数明确指定 this
值的情况下(无论是通过将函数添加为对象的方法,还是通过调用 call()
或 apply()
),this
值等于 Global 对象。而像这样通过简单地返回 this
来取得 Global 对象,在任何执行环境下都是可行的。
2.Math 对象
ECMAScript 还为保存数学公式和信息提供了一个公共信息,即 Math 对象。与在 JavaScript 直接编写的计算功能相比,Math 对象提供的计算功能执行起来要快得多。Math 对象中还提供了辅助完成这些计算的属性和方法。
2.1 Math 对象的属性
Math 对象包含的属性大都是数学计算中可能会用到的一些特殊值。下表列出了这些属性。
属性 | 说明 |
---|---|
Math.E | 自然对数的底数,即常量 e 的值 |
Math.LN10 | 10 的自然对数 |
Math.LN2 | 2 的自然对数 |
Math.LOG2E | 以 2 为底 e 的对数 |
Math.LOG10E | 以 10 为底 e 的对数 |
Math.PI | π 的值 |
Math.SQRT1_2 | 1/2 的平方根(即 2 的平方根的倒数) |
Math.SQRT2 | 2 的平方根 |
2.2 min() 和 max() 方法
Math 对象还包含许多方法,用于辅助完成简单和复杂的数学计算。
其中,min()
和 max()
方法用于确定一组数值中的最小值和最大值。这两个方法都可以接收任意多个数值参数,如下面的例子所示:
1 | var max = Math.max(3, 54, 32, 16); |
对于 3、54、32 和 16,Math.max()
返回 54,而 Math.min()
返回 3。这两个方法经常用于避免多余的循环和在 if 语句中确定一组数的最大值。
要找到数组中的最大或最小值,可以像下面这样使用 apply()
方法。
1 | var values= [1, 2, 3, 4, 5, 6, 7, 8]; |
这个技巧的关键是把 Math 对象作为 apply()
的第一个参数,从而正确地设置 this
值。然后,可以将任何数组作为第二个参数。
2.3 舍入方法
以下几个方法可以将小数值舍入为整数值:Math.ceil()
、Math.floor()
和 Math.round()
。这三个方法分别遵循下列舍入规则:
- Math.ceil() 执行向上舍入,即它总是将数值向上舍入为最接近的整数;
- Math.floor() 执行向下舍入,即它总是将数值向下舍入为最接近的整数;
- Math.round() 执行标准舍入,即它总是将数值四舍五入为最接近的整数。
2.4 random() 方法
Math.random()
方法返回大于等于 0 小于 1 的一个随机数。这个方法在某些时候非常实用。套用下面的公式,就可以利用 Math.random()
从某个整数范围内随机选择一个值。
1 | 值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值) |
公式中用到了 Math.floor()
方法,这是因为 Math.random()
总返回一个小数值。而用这个小数值乘以一个整数,然后再加上一个整数,最终结果仍然还是一个小数。举例来说,如果你想选择一个 1 到 10 之间的数值,可以像下面这样编写代码:
1 | var num = Math.floor(Math.random() * 10 + 1); |
总共有 10 个可能的值,而第一个可能的值是 1。而如果想要选择一个介于 2 和 10 之间的值,就应该将上面的代码改成这样:
1 | var num = Math.floor(Math.random() * 9 + 2); |
多数情况下,其实都可以通过一个函数来计算可能值的总数和第一个可能的值,例如:
1 | function selectFrom(lowerValue, upperValue) { |
函数 selectFrom()
接受两个参数:应该返回的最小值和最大值。利用这个函数,可以方便地从数组中随机取出一项,例如:
1 | var colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"]; |
这个例子中,传递给 selectFrom()
的第二个参数是数组的长度减 1,也就是数组中最后一项的位置。
2.5 其他方法
Math 对象中还包含其他一些与完成各种简单或复杂计算有关的方法,下表列出了上面没有介绍到的 Math 对象的方法。
方法 | 说明 |
---|---|
Math.abs(num) | 返回 num 的绝对值 |
Math.exp(num) | 返回 Math.E 的 num 次幂 |
Math.log(num) | 返回 num 的自然对数 |
Math.pow(num, power) | 返回 num 的 power 次幂 |
Math.sqrt(num) | 返回 num 的平方根 |
Math.acos(x) | 返回 x 的反余弦值 |
Math.asin(x) | 返回 x 的反正弦值 |
Math.atan(x) | 返回 x 的反正切值 |
Math.atan2(y, x) | 返回 y/x 的反正切值 |
Math.cos(x) | 返回 x 的余弦值 |
Math.sin(x) | 返回 x 的正弦值 |
Math.tan(x) | 返回 x 的正切值 |
ECMA-262 规定了这些方法,但不同实现可能会对这些方法采用不同的算法。因此,这些方法在不同的实现中可能会有不同的精度。