(五)
单体内置对象
ECMA-262对内置对象的定义是:“由ES实现提供的、不依赖宿主环境的对象,这些对象在ES程序执行之前就已经存在了。”意思就是说,开发人员不必显式地实例化内置对象,因为它们已经实例化了。前面我们已经介绍了大多数内置对象,例如Object、Array和String。ECMS-262还定义了两个单体内置对象:Global和Math。
Global对象
ES中的Global对象,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是Global对象的属性。诸如isNaN()、isFinite()、parseInt()以及parseFloat()实际上全都是Global对象的方法。
1、URI编码方法
Global对象的encodeURI()和encodeURIComponent()方法可以对URI(Uniform Resource
Identifiers,通用资源标识符)进行编码,以便发送给浏览器。有效的URI中不能包含某些字符,例如空格。而这两个URI编码方法就可以对URI进行编码,它们用特殊的UTF-8编码替换所有无效的字符,从而让浏览器能够接受和理解。
其中,encodeURI()主要用于整个URI(例如,http://www.wrox.com/illegal value.htm),而encodeURIComponent()主要用于对URI中的某一段(例如前面URI中illegal value.htm)进行编码。
它们的主要区别在于,encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和井字号;而encodeURIComponent()则会对它发现的任何非标准字符进行编码:
var uri = "http://www.wrox.com/illegal value.htm#start";
//"http://www.wrox.com/illegal%20value.htm#start"
alert(encodeURI(uri));
//"http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"
alert(encodeURIComponent(uri));
使用encodeURI()编码后的结果是除了空格之外的其他字符都原封不动,只有空格被替换成了%2。而encodeURIComponent()方法则会使用对应的编码替换所有非字母数字字符。这也正是可以对整个URI使用encodeURI(),而只能对附加在现有URI后面的字符串使用encodeURIComponent()的原因所在。
一般来说,我们使用encodeURIComponent()方法的时候要比使用encodeURI()更多,因为在实践中更常见的是对查询字符串参数而不是对基础URI进行编码。
与encodeURI()和encodeURIComponent()方法对应的两个方法分别是decodeURI()和decodeURIComponent()。其中,decodeURI()只能对使用encodeURI()替换的字符进行解码。例如,它可将%20替换成一个空格,但不会对%23作任何处理,因为%23表示井字号,而井字号不是使用encodeURI()替换的。同样地,decodeURIComponent()能够解码使用encodeURIComponent()编码的所有字符,即它可以解码任何特殊字符的编码:
var uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start";
//http%3A%2F%2Fwww.wrox.com%2Fillegal value.htm%23start
alert(decodeURI(uri));
//http://www.wrox.com/illegal value.htm#start
alert(decodeURIComponent(uri));
2、eval()方法
eval()大概是整个ES中最强大的一个方法。它就像是一个完整的ES解析器,它只接受一个参数,即要执行的ES(或js)字符串:
eval("alert('hi')");
这行代码的作用等价于:
alert("hi");
在eval()中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符串中;它们只在eval()执行的时候创建。
严格模式下,在外部访问不到eval()中创建的任何变量或函数。同样在严格模式下,为eval赋值也会导致错误:
"use strict";
eval = "hi"; //causes error
能够解释代码字符串的能力非常强大,但也非常危险。因此在使用eval()时必须极为谨慎,特别是在用它执行用户输入数据的情况下。否则,可能会有恶意用户输入威胁你的站点或应用程序安全的代码(所谓的代码注入)
3、Global对象的属性

ES5明确禁止给undefined、NaN和Infinity赋值,这样做即使在非严格模式下也会导致错误。
4、window对象
ES虽然没有指出如何直接访问Global对象,但Web浏览器都是将这个全局对象作为window对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,就都成为了window对象的属性:
var color = "red";
function sayColor(){
alert(window.color);
}
window.sayColor(); //"red"
JavaScript中的window对象除了扮演ES规定的Global对象的角色外,还承担了很多别的任务。将在后面讨论。
Math对象
1、min()和max()方法
var max = Math.max(3, 54, 32, 16);
alert(max); //54
var min = Math.min(3, 54, 32, 16);
alert(min); //3
要找到数组中的最大或最小值,可以像下面这样使用apply()方法。
var values = [1, 2, 3, 4, 5, 6, 7, 8];
var max = Math.max.apply(Math, values);
这个技巧的关键是把Math对象作为apply()的第一个参数,从而正确地设置this值。然后,可以将任何数组作为第二个参数。
2、舍入方法
- Math.ceil()执行向上舍入。
- Math.floor()执行向下舍入。
- Math.round()执行标准舍入。
alert(Math.ceil(25.9)); //26
alert(Math.ceil(25.5)); //26
alert(Math.ceil(25.1)); //26
alert(Math.round(25.9)); //26
alert(Math.round(25.5)); //26
alert(Math.round(25.1)); //25
alert(Math.floor(25.9)); //25
alert(Math.floor(25.5)); //25
alert(Math.floor(25.1)); //25
4、random()方法
Math.random()方法返回大于等于0小于1的一个随机数。对于某些站点来说,这个方法非常实用,因为可以利用它来随机显示一些名人名言和新闻事件。套用下面的公式,就可以利用Math.random()从某个整数范围内随机选择一个值:
值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)
公式中用到了Math.floor()方法,这是因为Math.random()总返回一个小数值。而用这个小数值乘以一个整数,然后再加上一个整数,最终结果仍然还是一个小数,例如,想选择一个1到10之间的数值:
var num = Math.floor(Math.random() * 10 + 1);
想选择一个介于2到10之间的值:
var num = Math.floor(Math.random() * 9 + 2);
面相对象的程序设计
ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数”。严格来说,就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。
属性类型
ECMA-262第5版在定义只有内部才用的特性时,描述了属性的各种特性。ECMA-262定义这些特性是为了实现JavaScript引擎用的,因此在JavaScript中不能直接访问它们。为了表示特性时内部值,该规范把它们放在了两对儿方括号中,例如[[Enumerable]]。
ECMAScript中两种属性:数据属性和访问器属性。
1、数据属性
数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性:
- [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者呢能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
- [[Enumerable]]:表示能否通过for-in循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
- [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
- [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存早这个位置。这个特性的默认值为undefined。
对于像前面例子中那样直接在对象上定义的属性,它们的[[Configurable]]、[[Enumerable]]和[[Writable]]特性都被设置为true,而[[Value]]特性被设置为指定的值:
var person = {
name: "Nicholas"
};
这里创建了一个名为name的属性,为它指定的值是“Nicholas”。也就是说,[[Value]]特性将被设置为“Nicholas”,而对这个值的任何修改都将反映在这个位置。
要修改属性默认的特性,必须使用ECMAScript5的Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descriptor)对象的属性必须是:configurable、enumerable、writable和value。设置其中的一或多个值,可以修改对应的特性值:
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
这个例子创建了一个名为name的属性,它的值“Nicholas”是只读的。这个属性的值是不可修改的,如果尝试为它指定新值,则在非严格模式下,赋值操作将被忽略;在严格模式下,赋值操作将会导致抛出错误。
类似的规则也适用于不可配置的属性:
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
delete person.name;
alert(person.name); //"Nicholas"
把configurable设置为false,表示不能从对象中删除属性。如果对这个属性调用delete,则在非严格模式下什么也不会发生,而在严格模式下会导致错误。而且,一旦把属性定义为不可配置的,就不能再把它变回可配置了。此时,在调用Object.defineProperty()方法修改除writable之外的特性,都会导致错误:
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
//抛出错误
Object.defineProperty(person, "name", {
configurable: true,
value: "Nicholas"
});
也就是说,可以多次调用Object.defineProperty()方法修改同一个属性,但在把configurable特性设置为false之后就会有限制了。
在调用Object.defineProperty()方法时,如果不指定,configurable、enumerable和writable特性的默认值都是false。多数情况下,可能都没有必要利用Object.defineProperty()方法提供的这些高级功能。不过,理解这些概念对理解JavaScript对象却非常有用。
2、访问器属性
访问器属性不包含数据值;它们包含一对儿getter和setter函数(不过,这两个函数都不是必须的)。访问器属性有如下4个特性:
- [[Configurable]]:。。。
- [[Enumerable]]:。。。
- [[Get]]:在读取属性时调用的函数。默认值为undefined。
- [[Set]]:在写入属性时调用的函数。默认值为undefined。
访问器属性不能直接定义,必须使用Object.defineProperty()来定义:
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); //2
_year前面的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性。
不一定非要同时指定getter和setter。只指定getter意味着属性是不能写,尝试写入属性会被忽略。在严格模式下,尝试写入只指定了getter函数的属性会抛出错误。类似地,只指定setter函数的属性也不能读,否则在非严格模式下会返回undefined,而在严格模式下行会抛出错误。
支持ES5的这个方法的浏览器有IE9+(IE8只是部分实现)、Firefox4+、Safari 5+、Opera12+和Chrome。在这个方法之前,要创建访问器属性,一般都是用两个非标准的方法:defineGetter()和defineSetter()。这两个方法最初是由Firefox引入的,后来Safari3、Chrome 1和Opera9.5也给出了相同的实现。使用这两个遗留的方法,可以像下面这样重写前面的例子:
var book = {
_year: 2004,
edition: 1
};
//定义访问器的旧方法
book.__defineGetter__("year", function () {
return this._year;
});
book.__defineSetter__("year", function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
});
在不支持Object.defineProperty()方法的浏览器中不能修改[[Configurable]]和[[Enumerable]]。