js高级程序设计(五)引用类型

时间:2023-03-09 03:24:51
js高级程序设计(五)引用类型

Object类型

创建Object 实例的方式有两种。第一种是使用new 操作符后跟Object 构造函数。

var person = new Object();
person.name = "Nicholas";
person.age = 29;

另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。

var person = {
  name : "Nicholas",
  age : 29
};

在这个例子中,左边的花括号({)表示对象字面量的开始,因为它出现在了表达式上下文(expression context)中。ECMAScript 中的表达式上下文指的是能够返回一个值(表达式)。赋值操作符表示后面是一个值,所以左花括号在这里表示一个表达式的开始。同样的花括号,如果出现在一个语句上下文(statement context)中,例如跟在if 语句条件的后面,则表示一个语句块的开始。

我们定义了name 属性,之后是一个冒号,再后面是这个属性的值。在对象字面量中,使用逗号来分隔不同的属性,因此"Nicholas"后面是一个逗号。但是,在age 属性的值29 的后面不能添加逗号,因为age 是这个对象的最后一个属性。在最后一个属性后面添加逗号,会在IE7 及更早版本和Opera 中导致错误。

在使用对象字面量语法时,属性名也可以使用字符串。

var person = {
"name" : "Nicholas",
"age" : 29,
5 : true
};

使用对象字面量语法时,如果留空其花括号,则可以定义只包含默认属性和方法的对象。

var person = {}; //与new Object()相同
person.name = "Nicholas";
person.age = 29;

在通过对象字面量定义对象时,实际上不会调用Object 构造函数。

访问对象属性时使用的都是点表示法,在JavaScript 也可以使用方括号表示法来访问对象的属性。

alert(person["name"]); //"Nicholas"
alert(person.name); //"Nicholas"

从功能上看,这两种访问对象属性的方法没有任何区别。但方括号语法的主要优点是可以通过变量来访问属性。

var propertyName = "name";
alert(person[propertyName]); //"Nicholas"

如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括号表示法。

person["first name"] = "Nicholas";

除非必须使用变量来访问属性,否则我们建议使用点表示法。

Array 类型

ECMAScript数组的每一项可以保存任何类型的数据,而且,ECMAScript数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据。

创建数组的基本方式有两种。第一种是使用Array构造函数。

var colors = new Array();

如果预先知道数组要保存的项目数量,也可以给构造函数传递length。

var colors = new Array(20);  //length 值为20

也可以向Array构造函数传递数组中应该包含的项。

var colors = new Array("red", "blue", "green");

在使用Array 构造函数时也可以省略new操作符。

var colors = Array(3); // 创建一个包含3 项的数组
var names = Array("Greg"); // 创建一个包含1 项,即字符串"Greg"的数组

创建数组的第二种基本方式是使用数组字面量表示法。数组字面量由一对包含数组项的方括号表示,多个数组项之间以逗号隔开。

var colors = ["red", "blue", "green"]; // 创建一个包含3 个字符串的数组
var names = []; // 创建一个空数组
var values = [1,2,]; // 不要这样!这样会创建一个包含2 或3 项的数组
var options = [,,,,,]; // 不要这样!这样会创建一个包含5 或6 项的数组

第三行展示了在数组字面量的最后一项添加逗号的结果:在IE中,values会成为一个包含3个项且每项的值分别为1、2和undefined的数组;在其他浏览器中,values 会成为一个包含2项且值分别为1和2的数组。原因是IE8及之前版本中的ECMAScript实现在数组字面量方面存在bug。由于这个bug导致的另一种情况如最后一行代码所示,该行代码可能会创建包含5 项的数组(在IE9+、Firefox、Opera、Safari 和Chrome 中),也可能会创建包含6 项的数组(在IE8 及更早版本中)。

与对象一样,在使用数组字面量表示法时,也不会调用Array 构造函数。

在读取和设置数组的值时,要使用方括号并提供相应值的基于0 的数字索引。

var colors = ["red", "blue", "green"]; // 定义一个字符串数组
alert(colors[0]); // 显示第一项
colors[2] = "black"; // 修改第三项
colors[3] = "brown"; // 新增第四项

数组的项数保存在其length属性中,这个属性始终会返回0或更大的值。

var colors = ["red", "blue", "green"]; // 创建一个包含3 个字符串的数组
var names = []; // 创建一个空数组
alert(colors.length); //
alert(names.length); //

数组的length属性很有特点——它不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。

var colors = ["red", "blue", "green"]; // 创建一个包含3 个字符串的数组
colors.length = 2;
alert(colors[2]); //undefined

如果将其length属性设置为大于数组项数的值,则新增的每一项都会取得undefined值。

var colors = ["red", "blue", "green"]; // 创建一个包含3 个字符串的数组
colors.length = 4;
aler t(colors[3]); //undefined

利用length属性也可以方便地在数组末尾添加新项。

var colors = ["red", "blue", "green"]; // 创建一个包含3 个字符串的数组
colors[colors.length] = "black"; //(在位置3)添加一种颜色
colors[colors.length] = "brown"; //(在位置4)再添加一种颜色

检测数组

使用 instanceof 操作符确定某个对象是不是数组。

if (value instanceof Array){
//对数组执行某些操作
}

instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数。如果你从
一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

ECMAScript 5 新增了 Array.isArray() 方法。这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。

if (Array.isArray(value)){
//对数组执行某些操作
}

支持 Array.isArray() 方法的浏览器有 IE9+、Firefox 4+、Safari 5+、Opera 10.5+和 Chrome。

转换方法

所有对象都具有 toLocaleString() 、 toString() 和 valueOf() 方法。调用数组的 toString() 方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。而调用 valueOf() 返回的还是数组本身。实际上,为了创建这个字符串会调用数组每一项的 toString() 方法。

var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
alert(colors.toString()); // red,blue,green
alert(colors.valueOf()); // red,blue,green
alert(colors); // red,blue,green

第三行把数组直接传给alert() 要接收字符串参数,所以它会在后台调用 toString() 方法,由此会得到与直接调用 toString() 方法相同的结果。

当调用数组的 toLocaleString() 方法时,它也会创建一个数组值的以逗号分隔的字符串。而与前两个方法唯一的不同之处在于,这一次为了取得每一项的值,调用的是每一项的 toLocaleString() 方法,而不是 toString() 方法。

var person1 = {
  toLocaleString : function () {
    return "Nikolaos";
  },
  toString : function() {
    return "Nicholas";
  }
};
var person2 = {
  toLocaleString : function () {
    return "Grigorios";
  },
  toString : function() {
    return "Greg";
  }
};
var people = [person1, person2];
alert(people); //Nicholas,Greg
alert(people.toString()); //Nicholas,Greg
alert(people.toLocaleString()); //Nikolaos,Grigorios

数组继承的 toLocaleString() 、 toString() 和 valueOf() 方法,在默认情况下都会以逗号分隔的字符串的形式返回数组项。而如果使用 join() 方法,则可以使用不同的分隔符来构建这个字符串。 join() 方法只接收一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串。

var colors = ["red", "green", "blue"];
alert(colors.join(",")); //red,green,blue
alert(colors.join("||")); //red||green||blue

如果数组中的某一项的值是 null 或者 undefined ,那么该值在 join() 、toLocaleString() 、 toString() 和 valueOf() 方法返回的结果中以空字符串表示。

栈方法

ECMAScript 数组也提供了一种让数组的行为类似栈一样,是一种可以限制插入和删除项的数据结构。栈是一种 LIFO(Last-In-First-Out,后进先出)的数据结构,也就是最新添加的项最早被移除。而栈中项的插入(叫做推入)和移除(叫做弹出),只发生在一个位置——栈的顶部。ECMAScript 为数组专门提供了 push() 和 pop() 方法。

push() 方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。而pop() 方法则从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。

var colors = new Array(); // 创建一个数组
var count = colors.push("red", "green"); // 推入两项
alert(count); //2 返回length
count = colors.push("black"); // 推入另一项
alert(count); //3 返回length
var item = colors.pop(); // 取得最后一项
alert(item); //"black"
alert(colors.length); //

队列方法

使用shift() ,它能够移除数组中的第一个项并返回该项,同时将数组长度减 1。结合使用 shift() 和 push() 方法,可以像使用队列一样使用数组。

var colors = new Array(); //创建一个数组
var count = colors.push("red", "green"); //推入两项
alert(count); // count = colors.push("black"); //推入另一项
alert(count); // var item = colors.shift(); // 取得第一项
alert(item); //"red"
alert(colors.length); //

ECMAScript 还为数组提供了一个 unshift() 方法。顾名思义, unshift() 与 shift() 的用途相反:它能在数组前端添加任意个项并返回新数组的长度。同时使用 unshift() 和 pop() 方法,可以从相反的方向来模拟队列,即在数组的前端添加项,从数组末端移除项。

IE7 及更早版本对 JavaScript 的实现中存在一个偏差,其 unshift() 方法总是返回 undefined 而不是数组的新长度。IE8 在非兼容模式下会返回正确的长度值。

重排序方法

reverse() 和 sort()。

在默认情况下, sort() 方法按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。为了实现排序, sort() 方法会调用每个数组项的 toString() 转型方法,然后比较得到的字符串,以确定如何排序。即使数组中的每一项都是数值, sort() 方法比较的也是字符串。

var values = [0, 1, 5, 10, 15];
values.sort();
alert(values); //0,1,10,15,5

数值 5 虽然小于 10,但在进行字符串比较时, "10" 则位于 "5" 的前面,于是数组的顺序就被修改了。

因此 sort() 方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。

比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回 0,如果第一个参数应该位于第二个之后则返回一个正数。

function compare(value1, value2) {
if (value1 < value2) {
  return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
var values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); //0,1,5,10,15

对于数值类型或者其 valueOf() 方法会返回数值类型的对象类型,可以使用一个更简单的比较函数。这个函数只要用第二个值减第一个值即可。

function compare(value1, value2){
return value2 - value1;
}

排序函数的简洁写法:

var p = [5, 2, 3, 1, 7, 5, 6, 9, 6, 0];
function down(a, b) {
return (a < b) ? 1 : -1
}
p.sort(down)
alert(p)

json排序

var p = [
{name:"kitty", age:12},
{name:"sonny", age:9},
{name:"jake", age:13},
{name:"fun", age:24}
];
function down(x, y) {
return (x.age < y.age) ? 1 : -1
}
p.sort(down)

操作方法

concat()方法可以基于当前数组中的所有项创建一个新数组。

这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给concat()方法传递参数的情况下,它只是
复制当前数组并返回副本。

var colors = ["red", "green", "blue"];
var colors2 = colors.concat("yellow", ["black", "brown"]);
alert(colors); //red,green,blue
alert(colors2); //red,green,blue,yellow,black,brown

slice(),它能够基于当前数组中的一或多个项创建一个新数组。

slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。

var colors = ["red", "green", "blue", "yellow", "purple"];
var colors2 = colors.slice(1);
var colors3 = colors.slice(1,4);
alert(colors2); //green,blue,yellow,purple
alert(colors3); //green,blue,yellow

splice()

删除:可以删除任意数量的项,只需指定2 个参数:要删除的第一项的位置和要删除的项数。例如,splice(0,2)会删除数组中的前两项。

插入:可以向指定位置插入任意数量的项,只需提供3 个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,"red","green")会从当前数组的位置2 开始插入字符串"red"和"green"。

替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,"red","green")会删除当前数组位置2 的项,然后再从位置2 开始插入字符串"red"和"green"。

splice()方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)

var colors = ["red", "green", "blue"];
var removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue
alert(removed); // red,返回的数组中只包含一项 removed = colors.splice(1, 0, "yellow", "orange"); // 从位置1 开始插入两项
alert(colors); // green,yellow,orange,blue
alert(removed); // 返回的是一个空数组

removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow,返回的数组中只包含一项

位置方法

indexOf()和lastIndexOf()。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf()方法从数组的开头(位
置0)开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找。

这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1。

var numbers = [1,2,3,4,5,4,3,2,1];
alert(numbers.indexOf(4)); //
alert(numbers.lastIndexOf(4)); // alert(numbers.indexOf(4, 4)); //
alert(numbers.lastIndexOf(4, 4)); // var person = { name: "Nicholas" };
var people = [{ name: "Nicholas" }];
var morePeople = [person]; alert(people.indexOf(person)); //-1
alert(morePeople.indexOf(person)); //

迭代方法

ECMAScript 5 为数组定义了5 个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响this 的值。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。

every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。

filter():对数组中的每一项运行给定函数,返回该函数会返回true 的项组成的数组。

forEach():对数组中的每一项运行给定函数。这个方法没有返回值。

map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。

some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。

在这些方法中,最相似的是every()和some()。它们都用于查询数组中的项是否满足某个条件。

var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item, index, array){
  return (item > 2);
});
alert(everyResult); //false

var someResult = numbers.some(function(item, index, array){
  return (item > 2);
});
alert(someResult); //true

filter()函数,返回满足条件的项。

var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item, index, array){
  return (item > 2);
});
alert(filterResult); //[3,4,5,4,3]

map()也返回一个数组,返回原始数组每一项运行函数后的结果组成的数组。

var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item, index, array){
  return item * 2;
});
alert(mapResult); //[2,4,6,8,10,8,6,4,2]

forEach(),它只是对数组中的每一项运行传入的函数。这个方法没有返回值,本质上与使用for 循环迭代数组一样。

var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item, index, array){
//执行某些操作
});

支持这些迭代方法的浏览器有IE9+、Firefox 2+、Safari 3+、Opera 9.5+和Chrome。

归并方法

ECMAScript 5 还新增了两个归并数组的方法:reduce()和reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中,reduce()方法从数组的第一项开始,逐个遍历到最后。而reduceRight()则从数组的最后一项开始,向前遍历到第一项。

这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。

传给reduce()和reduceRight()的函数接收4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。

使用reduce()方法可以执行求数组中所有值之和的操作

var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //

reduceRight()的作用类似,只不过方向相反而已

var values = [1,2,3,4,5];
var sum = values.reduceRight(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //

支持这两个归并函数的浏览器有IE9+、Firefox 3+、Safari 4+、Opera 10.5 和Chrome。

Date 类型

ECMAScript 中的Date 类型是在早期Java 中的java.util.Date 类基础上构建的。为此,Date类型使用自UTC(Coordinated Universal Time,国际协调时间)1970 年1 月1 日午夜(零时)开始经过的毫秒数来保存日期。在使用这种数据存储格式的条件下,Date 类型保存的日期能够精确到1970 年1
月1 日之前或之后的285 616 年。

创建一个日期对象,使用new 操作符和Date 构造函数即可

var now = new Date();

在调用Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数(即从UTC 时间1970 年1 月1 日午夜起至该日期止经过的毫秒数)。为了简化这一计算过程,ECMAScript 提供了两个方法:Date.parse()和Date.UTC()。

Date.parse()方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数。

ECMA-262 没有定义Date.parse()应该支持哪种日期格式,因此这个方法的行为因实现而异,而且通常是因地区而异。将地区设置为美国的浏览器通常都接受下列日期格式:

“月/日/年”,如6/13/2004;
 “英文月名 日,年”,如January 12,2004;
 “英文星期几 英文月名 日 年 时:分:秒 时区”,如Tue May 25 2004 00:00:00 GMT-0700。
 ISO 8601 扩展格式YYYY-MM-DDTHH:mm:ss.sssZ(例如2004-05-25T00:00:00)。只有兼容ECMAScript 5 的实现支持这种格式。

例如,要为2004 年5 月25 日创建一个日期对象,可以使用下面的代码:

var someDate = new Date(Date.parse("May 25, 2004"));

如果传入Date.parse()方法的字符串不能表示日期,那么它会返回NaN。实际上,如果直接将表示日期的字符串传递给Date 构造函数,也会在后台调用Date.parse()。

var someDate = new Date(Date.parse("May 25, 2004"));
//等价
var someDate = new Date("May 25, 2004");

Date.UTC()方法同样也返回表示日期的毫秒数,但它与Date.parse()在构建值时使用不同的信息。Date.UTC()的参数分别是年份、基于0 的月份(一月是0,二月是1,以此类推)、月中的哪一天(1 到31)、小时数(0 到23)、分钟、秒以及毫秒数。在这些参数中,只有前两个参数(年和月)是必需的。如果没有提供月中的天数,则假设天数为1;如果省略其他参数,则统统假设为0。

// GMT 时间2000 年1 月1 日午夜零时
var y2k = new Date(Date.UTC(2000, 0));
// GMT 时间2005 年5 月5 日下午5:55:55
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

如同模仿Date.parse()一样,Date 构造函数也会模仿Date.UTC(),但有一点明显不同:日期和时间都基于本地时区而非GMT 来创建。不过,Date 构造函数接收的参数仍然与Date.UTC()相同。因此,如果第一个参数是数值,Date 构造函数就会假设该值是日期中的年份,而第二个参数是月份,以此类推。

// 本地时间2000 年1 月1 日午夜零时
var y2k = new Date(2000, 0);
// 本地时间2005 年5 月5 日下午5:55:55
var allFives = new Date(2005, 4, 5, 17, 55, 55);

ECMAScript 5 添加了Data.now()方法,返回表示调用这个方法时的日期和时间的毫秒数。

//取得开始时间
var start = Date.now();
//调用函数
doSomething();
//取得停止时间
var stop = Date.now(),
result = stop – start;

支持Data.now()方法的浏览器包括IE9+、Firefox 3+、Safari 3+、Opera 10.5 和Chrome。在不支持它的浏览器中,使用+操作符把Data 对象转换成字符串,也可以达到同样的目的。

//取得开始时间
var start = +new Date();
//调用函数
doSomething();
//取得停止时间
var stop = +new Date(),
result = stop - start;

继承的方法

与其他引用类型一样,Date 类型也重写了toLocaleString()、toString()和valueOf()方法;但这些方法返回的值与其他类型中的方法不同。Date 类型的toLocaleString()方法会按照与浏览器设置的地区相适应的格式返回日期和时间。这大致意味着时间格式中会包含AM 或PM,但不会包含时区信息(当然,具体的格式会因浏览器而异)。而toString()方法则通常返回带有时区信息的日期和时间,其中时间一般以军用时间(即小时的范围是0 到23)表示。下面给出了在不同浏览器中调用toLocaleString()和toString()方法,输出PST(Pacific Standard Time,太平洋标准时间)时间2007年2 月1 日午夜零时的结果。

Internet Explorer 8
toLocaleString() — Thursday, February 01, 2007 12:00:00 AM
toString() — Thu Feb 1 00:00:00 PST 2007 Firefox 3.5
toLocaleString() — Thursday, February 01, 2007 12:00:00 AM
toString() — Thu Feb 01 2007 00:00:00 GMT-0800 (Pacific Standard Time) Safari 4
toLocaleString() — Thursday, February 01, 2007 00:00:00
toString() — Thu Feb 01 2007 00:00:00 GMT-0800 (Pacific Standard Time) Chrome 4
toLocaleString() — Thu Feb 01 2007 00:00:00 GMT-0800 (Pacific Standard Time)
toString() — Thu Feb 01 2007 00:00:00 GMT-0800 (Pacific Standard Time) Opera 10
toLocaleString() — 2/1/2007 12:00:00 AM
toString() — Thu, 01 Feb 2007 00:00:00 GMT-0800

至于Date 类型的valueOf()方法,则根本不返回字符串,而是返回日期的毫秒表示。因此,可以方便使用比较操作符(小于或大于)来比较日期值。

var date1 = new Date(2007, 0, 1); //"January 1, 2007"
var date2 = new Date(2007, 1, 1); //"February 1, 2007"
alert(date1 < date2); //true
alert(date1 > date2); //false

日期格式化方法

Date 类型还有一些专门用于将日期格式化为字符串的方法,这些方法如下。
toDateString()——以特定于实现的格式显示星期几、月、日和年;
toTimeString()——以特定于实现的格式显示时、分、秒和时区;
toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;
toLocaleTimeString()——以特定于实现的格式显示时、分、秒;
toUTCString()——以特定于实现的格式完整的UTC 日期。
与toLocaleString()和toString()方法一样,以上这些字符串格式方法的输出也是因浏览器而异的,因此没有哪一个方法能够用来在用户界面中显示一致的日期信息。

日期/时间组件方法

Date 类型的方法(如下表所示),都是直接取得和设置日期值中特定部分的方法了。需要注意的是,UTC 日期指的是在没有时区偏差的情况下(将日期转换为GMT 时间)的日期值。

js高级程序设计(五)引用类型

RegExp 类型

ECMAScript 通过RegExp 类型来支持正则表达式。

var expression = / pattern / flags ; //创建一个正则表达式

其中的模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。正则表达式的匹配模式支持下列3 个标志。

  • g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;

  • i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
  • m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。

/*
* 匹配字符串中所有"at"的实例
*/
var pattern1 = /at/g;
/*
* 匹配第一个"bat"或"cat",不区分大小写
*/
var pattern2 = /[bc]at/i;
/*
* 匹配所有以"at"结尾的3 个字符的组合,不区分大小写
*/
var pattern3 = /.at/gi;

与其他语言中的正则表达式类似,模式中使用的所有元字符都必须转义。正则表达式中的元字符包括:

( [ { \ ^ $ | ) ? * + .]}

这些元字符在正则表达式中都有一或多种特殊用途,因此如果想要匹配字符串中包含的这些字符,就必须对它们进行转义。

/*
* 匹配第一个"bat"或"cat",不区分大小写
*/
var pattern1 = /[bc]at/i;
/*
* 匹配第一个" [bc]at",不区分大小写
*/
var pattern2 = /\[bc\]at/i;
/*
* 匹配所有以"at"结尾的3 个字符的组合,不区分大小写
*/
var pattern3 = /.at/gi;
/*
* 匹配所有".at",不区分大小写
*/
var pattern4 = /\.at/gi;

前面举的这些例子都是以字面量形式来定义的正则表达式。另一种创建正则表达式的方式是使用RegExp 构造函数,它接收两个参数:一个是要匹配的字符串模式,另一个是可选的标志字符串。可以使用字面量定义的任何表达式,都可以使用构造函数来定义

/*
* 匹配第一个"bat"或"cat",不区分大小写
*/
var pattern1 = /[bc]at/i;
/*
* 与pattern1 相同,只不过是使用构造函数创建的
*/
var pattern2 = new RegExp("[bc]at", "i");

传递给RegExp 构造函数的两个参数都是字符串。由于RegExp 构造函数的模式参数是字符串,所以在某些情况下要对字符进行双重转义。所有元字符都必须双重转义,那些已经转义过的字符也是如此。

js高级程序设计(五)引用类型

使用正则表达式字面量和使用RegExp 构造函数创建的正则表达式不一样。在ECMAScript 3 中,正则表达式字面量始终会共享同一个RegExp 实例,而使用构造函数创建的每一个新RegExp 实例都是一个新实例。

var re = null,
  i;
for (i=0; i < 10; i++){
  re = /cat/g;
  re.test("catastrophe");
}
for (i=0; i < 10; i++){
  re = new RegExp("cat", "g");
  re.test("catastrophe");
}

在第一个循环中,即使是循环体中指定的,但实际上只为/cat/创建了一个RegExp 实例。由于实例属性(下一节介绍实例属性)不会重置,所以在循环中再次调用test()方法会失败。这是因为第一次调用test()找到了"cat",但第二次调用是从索引为3 的字符(上一次匹配的末尾)开始的,所以就找不到它了。由于会测试到字符串末尾,所以下一次再调用test()就又从开头开始了。

第二个循环使用RegExp 构造函数在每次循环中创建正则表达式。因为每次迭代都会创建一个新的RegExp 实例,所以每次调用test()都会返回true。

ECMAScript 5 明确规定,使用正则表达式字面量必须像直接调用RegExp 构造函数一样,每次都创建新的RegExp 实例。

RegExp实例属性

RegExp 的每个实例都具有下列属性

  • global:布尔值,表示是否设置了g 标志。
  • ignoreCase:布尔值,表示是否设置了i 标志。
  • lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0 算起。
  • multiline:布尔值,表示是否设置了m 标志。
  • source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。
var pattern1 = /\[bc\]at/i;
alert(pattern1.global); //false
alert(pattern1.ignoreCase); //true
alert(pattern1.multiline); //false
alert(pattern1.lastIndex); //
alert(pattern1.source); //"\[bc\]at" var pattern2 = new RegExp("\\[bc\\]at", "i");
alert(pattern2.global); //false
alert(pattern2.ignoreCase); //true
alert(pattern2.multiline); //false
alert(pattern2.lastIndex); //
aler t(pattern2.source); //"\[bc\]at"

RegExp实例方法

RegExp 对象的主要方法是exec(),该方法是专门为捕获组而设计的。exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回null。返回的数组虽然是Array 的实例,但包含两个额外的属性:index 和input。其中,index 表示匹配项在字符串中的位置,而input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。

var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;
var matches = pattern.exec(text);
alert(matches.index); //
alert(matches.input); // "mom and dad and baby"
alert(matches[0]); // "mom and dad and baby"
alert(matches[1]); // " and dad and baby"
aler t(matches[2]); // " and baby"

对于exec()方法而言,即使在模式中设置了全局标志(g),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用exec()将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用exec()则都会在字符串中继续查找新匹配项

var text = "cat, bat, sat, fat";

var pattern1 = /.at/;

var matches = pattern1.exec(text);
alert(matches.index); //
alert(matches[0]); //cat
alert(pattern1.lastIndex); // matches = pattern1.exec(text);
alert(matches.index); //
alert(matches[0]); //cat
alert(pattern1.lastIndex); // var pattern2 = /.at/g; var matches = pattern2.exec(text);
alert(matches.index); //
alert(matches[0]); //cat
alert(pattern2.lastIndex); // matches = pattern2.exec(text);
alert(matches.index); //
alert(matches[0]); //bat
alert(pattern2.lastIndex); //

正则表达式的第二个方法是test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回true;否则,返回false。

var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)){
  alert("The pattern was matched.");
}

RegExp 实例继承的toLocaleString()和toString()方法都会返回正则表达式的字面量,与创建正则表达式的方式无关。

var pattern = new RegExp("\\[bc\\]at", "gi");
alert(pattern.toString()); // /\[bc\]at/gi
alert(pattern.toLocaleString()); // /\[bc\]at/gi

正则表达式的valueOf()方法返回正则表达式本身。

RegExp构造函数属性

RegExp 构造函数包含一些属性(这些属性在其他语言中被看成是静态属性)。这些属性适用于作用域中的所有正则表达式,并且基于所执行的最近一次正则表达式操作而变化。关于这些属性的另一个独特之处,就是可以通过两种方式访问它们。换句话说,这些属性分别有一个长属性名和一个短属性名(Opera 是例外,它不支持短属性名)。

js高级程序设计(五)引用类型

使用这些属性可以从exec()或test()执行的操作中提取出更具体的信息。

var text = "this has been a short summer";
var pattern = /(.)hort/g;
/*
* 注意:Opera 不支持input、lastMatch、lastParen 和multiline 属性
* Internet Explorer 不支持multiline 属性
*/
if (pattern.test(text)){
  alert(RegExp.input); // this has been a short summer
  alert(RegExp.leftContext); // this has been a
  alert(RegExp.rightContext); // summer
  alert(RegExp.lastMatch); // short
  alert(RegExp.lastParen); // s
  alert(RegExp.multiline); // false
}

例子使用的长属性名都可以用相应的短属性名来代替。只不过,由于这些短属性名大都不是有效的ECMAScript 标识符,因此必须通过方括号语法来访问它们。

var text = "this has been a short summer";
var pattern = /(.)hort/g;
/*
* 注意:Opera 不支持input、lastMatch、lastParen 和multiline 属性
* Internet Explorer 不支持multiline 属性
*/
if (pattern.test(text)){
  alert(RegExp.$_); // this has been a short summer
  alert(RegExp["$`"]); // this has been a
  alert(RegExp["$'"]); // summer
  alert(RegExp["$&"]); // short
  alert(RegExp["$+"]); // s
  alert(RegExp["$*"]); // false
}

除了上面介绍的几个属性之外,还有多达9 个用于存储捕获组的构造函数属性。访问这些属性的语法是RegExp.$1、RegExp.$2…RegExp.$9,分别用于存储第一、第二……第九个匹配的捕获组。在调用exec()或test()方法时,这些属性会被自动填充。然后,我们就可以像下面这样来使用它们。

var text = "this has been a short summer";
var pattern = /(..)or(.)/g;
if (pattern.test(text)){
  alert(RegExp.$1); //sh
  alert(RegExp.$2); //t
}

模式的局限性

下面列出了ECMAScript 正则表达式不支持的特性。

  • 匹配字符串开始和结尾的\A 和\Z 锚(但支持以插入符号(^)和美元符号($)来匹配字符串的开始和结尾)
  • 向后查找(lookbehind)(但完全支持向前查找(lookahead))
  • 并集和交集类
  • 原子组(atomic grouping)
  • Unicode 支持(单个字符除外,如\uFFFF)
  • 命名的捕获组(但支持编号的捕获组)
  • s(single,单行)和x(free-spacing,无间隔)匹配模式
  • 条件匹配
  • 正则表达式注释

Function 类型

函数实际上是对象。每个函数都是Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

function sum (num1, num2) {
return num1 + num2;
}
var sum = function(num1, num2){
return num1 + num2;
};

最后一种定义函数的方式是使用Function 构造函数。Function 构造函数可以接收任意数量的参数,但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。

var sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐

我们不推荐读者使用这种方法定义函数,因为这种语法会导致解析两次代码(第一次是解析常规ECMAScript 代码,第二次是解析传入构造函数中的字符串),从而影响性能。不过,这种语法对于理解“函数是对象,函数名是指针”的概念倒是非常直观的。

由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字。

function sum(num1, num2){
return num1 + num2;
}
alert(sum(10,10)); //
var anotherSum = sum;
alert(anotherSum(10,10)); //
sum = null;
alert(anotherSum(10,10)); //

注意,使用不带圆括号的函数名是访问函数指针,而非调用函数。

没有重载(深入理解)

function addSomeNumber(num){
return num + 100;
}
function addSomeNumber(num) {
return num + 200;
}
var result = addSomeNumber(100); //
var addSomeNumber = function (num){
return num + 100;
};
addSomeNumber = function (num) {
return num + 200;
};
var result = addSomeNumber(100); //

在创建第二个函数时,实际上覆盖了引用第一个函数的变量addSomeNumber。

函数声明与函数表达式

解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

alert(sum(10,10));
function sum(num1, num2){
return num1 + num2;
}

以上代码完全可以正常运行。因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript 引擎也能把函数声明提升到顶部。

alert(sum(10,10)); //报错
var sum = function(num1, num2){
return num1 + num2;
};

以上代码之所以会在运行期间产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。换句话说,在执行到函数所在的语句之前,变量sum 中不会保存有对函数的引用;而且,由于第一行代码就会导致“unexpected identifier”(意外标识符)错误,实际上也不会执行到下一行。

作为值的函数

因为ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}

这个函数接受两个参数。第一个参数应该是一个函数,第二个参数应该是要传递给该函数的一个值。

function add10(num){
return num + 10;
}
var result1 = callSomeFunction(add10, 10);
alert(result1); //
function getGreeting(name){
return "Hello, " + name;
}
var result2 = callSomeFunction(getGreeting, "Nicholas");
alert(result2); //"Hello, Nicholas"

要访问函数的指针而不执行函数的话,必须去掉函数名后面的那对圆括号。因此上面例子中传递给callSomeFunction()的是add10 和getGreeting,而不是执行它们之后的结果。

可以从一个函数中返回另一个函数,而且这也是极为有用的一种技术。例如,假设有一个对象数组,我们想要根据某个对象属性对数组进行排序。而传递给数组sort()方法的比较函数要接收两个参数,即要比较的值。可是,我们需要一种方式来指明按照哪个属性来排序。要解决这个问题,可以定义一个函数,它接收一个属性名,然后根据这个属性名来创建一个比较函数。

function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}

这个函数定义看起来有点复杂,但实际上无非就是在一个函数中嵌套了另一个函数,而且内部函数前面加了一个return 操作符。在内部函数接收到propertyName 参数后,它会使用方括号表示法来取得给定属性的值。取得了想要的属性值之后,定义比较函数就非常简单了。

var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];
data.sort(createComparisonFunction("name"));
alert(data[0].name); //Nicholas
data.sort(createComparisonFunction("age"));
alert(data[0].name); //Zachary

函数内部属性

在函数内部,有两个特殊的对象:arguments 和this。其中,arguments 在第3 章曾经介绍过,它是一个类数组对象,包含着传入函数中的所有参数。虽然arguments 的主要用途是保存函数参数,但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments 对象的函数。
请看下面这个非常经典的阶乘函数。

function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
}
}

这个函数的执行与函数名factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee。

function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}

函数内部的另一个特殊对象是this,其行为与Java 和C#中的this 大致类似。换句话说,this引用的是函数据以执行的环境对象——或者也可以说是this 值(当在网页的全局作用域中调用函数时,this 对象引用的就是window)。

window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red"
o.sayColor = sayColor;
o.sayColor(); //"blue"

ECMAScript 5 也规范化了另一个函数对象的属性:caller。除了Opera 的早期版本不支持,其他浏览器都支持这个ECMAScript 3 并没有定义的属性。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。

function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();

以上代码会导致警告框中显示outer()函数的源代码。因为outer()调用了inner(),所以inner.caller 就指向outer()。为了实现更松散的耦合,也可以通过arguments.callee.caller
来访问相同的信息。

function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller);
}
outer();

IE、Firefox、Chrome 和Safari 的所有版本以及Opera 9.6 都支持caller 属性。

当函数在严格模式下运行时,访问arguments.callee 会导致错误。ECMAScript 5 还定义了arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller 和函数的caller 属性。以上变化都是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。

严格模式还有一个限制:不能为函数的caller 属性赋值,否则会导致错误。

函数属性和方法

每个函数都包含两个属性:length 和prototype。其中,length 属性表示函数希望接收的命名参数的个数。

function sayName(name){
alert(name);
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
alert("hi");
}
alert(sayName.length); //
alert(sum.length); //
alert(sayHi.length); //

每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是arguments 对象。

function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments); // 传入arguments 对象
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]); // 传入数组
}
alert(callSum1(10,10)); //
alert(callSum2(10,10)); //

call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来。

function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //

传递参数并非apply()和call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。

window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue

使用call()(或apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。

ECMAScript 5 还定义了一个方法:bind()。这个方法会创建一个函数的实例,其this 值会被绑定到传给bind()函数的值。

window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue

支持bind()方法的浏览器有IE9+、Firefox 4+、Safari 5.1+、Opera 12+和Chrome。

每个函数继承的toLocaleString()和toString()方法始终都返回函数的代码。返回代码的格式则因浏览器而异——有的返回的代码与源代码中的函数代码一样,而有的则返回函数代码的内部表示,即由解析器删除了注释并对某些代码作了改动后的代码。由于存在这些差异,我们无法根据这两个方法返回的结果来实现任何重要功能;不过,这些信息在调试代码时倒是很有用。另外一个继承的valueOf()方法同样也只返回函数代码。

基本包装类型

ECMAScript 还提供了3 个特殊的引用类型:Boolean、Number 和String。

var s1 = "some text";
var s2 = s1.substring(2);

此例子执行时,其实后台发生了下列处理。

(1) 创建String 类型的一个实例;
(2) 在实例上调用指定的方法;
(3) 销毁这个实例。
可以将以上三个步骤想象成是执行了下列ECMAScript 代码。

var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;

经过此番处理,基本的字符串值就变得跟对象一样了。而且,上面这三个步骤也分别适用于Boolean和Number 类型对应的布尔值和数字值。

引用类型与基本包装类型的主要区别就是对象的生存期。使用new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。

var s1 = "some text";
s1.color = "red";
alert(s1.color); //undefined

第二行创建的String 对象在执行第三行代码时已经被销毁了。第三行代码又创建自己的String 对象,而该对象没有color 属性。

对基本包装类型的实例调用typeof 会返回"object",而且所有基本包装类型的对象都会被转换为布尔值true。

Object 构造函数也会像工厂方法一样,根据传入值的类型返回相应基本包装类型的实例。

var obj = new Object("some text");
alert(obj instanceof String); //true

使用new 调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。

var value = "25";
var number = Number(value); //转型函数
alert(typeof number); //"number"
var obj = new Number(value); //构造函数
alert(typeof obj); //"object"

变量number 中保存的是基本类型的值25,而变量obj 中保存的是Number 的实例。

Boolean类型

Boolean 类型是与布尔值对应的引用类型。要创建Boolean 对象,可以像下面这样

var booleanObject = new Boolean(true);

Boolean 类型的实例重写了valueOf()方法,返回基本类型值true 或false;重写了toString()方法,返回字符串"true"和"false"。

var falseObject = new Boolean(false);
var result = falseObject && true;
alert(result); //true var falseValue = false;
result = falseValue && true;
alert(result); //false

布尔表达式中的所有对象都会被换为true,因此falseObject 对象在布尔表达式中代表的是true。结果,true && true 当然就等于true 了。

基本类型与引用类型的布尔值还有两个区别。首先,typeof 操作符对基本类型返回"boolean",而对引用类型返回"object"。其次,由于Boolean 对象是Boolean 类型的实例,所以使用instanceof操作符测试Boolean 对象会返回true,而测试基本类型的布尔值则返回false。

alert(typeof falseObject); //object
alert(typeof falseValue); //boolean
alert(falseObject instanceof Boolean); //true
alert(falseValue instanceof Boolean); //false

建议永远不要使用Boolean 对象。

Number类型

要创建Number 对象,可以

var numberObject = new Number(10);

Number 类型也重写了valueOf()、toLocaleString()和toString()方法。重写后的valueOf()方法返回对象表示的基本类型的数值,另外两个方法则返回字符串形式的数值。我们在第3 章还介绍过,可以为toString()方法传递一个表示基数的参数,告诉它返回几进制数值的字符串形式,如下面的例子

var num = 10;
alert(num.toString()); //"10"
alert(num.toString(2)); //"1010"
alert(num.toString(8)); //"12"
alert(num.toString(10)); //"10"
alert(num.toString(16)); //"a"

除了继承的方法之外,Number 类型还提供了一些用于将数值格式化为字符串的方法。

toFixed()方法会按照指定的小数位返回数值的字符串表示

var num = 10;
alert(num.toFixed(2)); //"10.00"

如果数值本身包含的小数位比指定的还多,那么接近指定的最大小数位的值就会舍入

var num = 10.005;
alert(num.toFixed(2)); //"10.01"

在给toFixed()传入0 的情况下,IE8 及之前版本不能正确舍入范围在{(-0.94,-0.5],[0.5,0.94)}之间的值。对于这个范围内的值,IE 会返回0,而不是-1 或1;其他
浏览器都能返回正确的值。IE9 修复了这个问题。

toExponential(),该方法返回以指数表示法(也称e 表示法)表示的数值的字符串形式。与toFixed()一样,toExponential()也接收一个参数,而且该参数同样也是指定输出结果中的小数位数。

var num = 10;
alert(num.toExponential(1)); //"1.0e+1"

对于一个数值来说,toPrecision()方法可能会返回固定大小(fixed)格式,也可能返回指数(exponential)格式;具体规则是看哪种格式最合适。这个方法接收一个参数,即表示数值的所有数字的位数(不包括指数部分)。

var num = 99;
alert(num.toPrecision(1)); //"1e+2"
alert(num.toPrecision(2)); //"99"
aler t(num.toPrecision(3)); //"99.0"

toPrecision()会根据要处理的数值决定到底是调用toFixed()还是调用toExponential()。而这三个方法都可以通过向上或向下舍入,做到以最准确的形式来表示带有正确小数位的值。

不建议直接实例化Number 类型,因为在使用typeof 和instanceof 操作符测试基本类型数值与引用类型数值时,得到的结果完全不同。

var numberObject = new Number(10);
var numberValue = 10;
alert(typeof numberObject); //"object"
alert(typeof numberValue); //"number"
alert(numberObject instanceof Number); //true
aler t(numberValue instanceof Number); //false

String类型

可以像下面这样使用String 构造函数来创建

var stringObject = new String("hello world");

String 对象的方法也可以在所有基本的字符串值中访问到。其中,继承的valueOf()、toLocaleString()和toString()方法,都返回对象所表示的基本字符串值。

String 类型的每个实例都有一个length 属性,表示字符串中包含多个字符。

var stringValue = "hello world";
alert(stringValue.length); //"11"

String 类型提供了很多方法

1字符方法

charAt()和charCodeAt()。这两个方法都接收一个参数,即基于0 的字符位置。

charAt()方法以单字符字符串的形式返回给定位置的那个字符

var stringValue = "hello world";
alert(stringValue.charAt(1)); //"e"

charCodeAt()返回字符编码。

var stringValue = "hello world";
alert(stringValue.charCodeAt(1)); //输出"101"

ECMAScript 5 还定义了另一个访问个别字符的方法。在支持此方法的浏览器中,可以使用方括号加数字索引来访问字符串中的特定字符。

var stringValue = "hello world";
alert(stringValue[1]); //"e"

使用方括号表示法访问个别字符的语法得到了IE8 及Firefox、Safari、Chrome 和Opera 所有版本的支持。

2字符串操作方法

concat(),用于将一或多个字符串拼接起来,返回拼接得到的新字符串。

var stringValue = "hello ";
var result = stringValue.concat("world");
alert(result); //"hello world"
alert(stringValue); //"hello"

concat()方法可以接受任意多个参数,也就是说可以通过它拼接任意多个字符串。

var stringValue = "hello ";
var result = stringValue.concat("world", "!");
alert(result); //"hello world!"
alert(stringValue); //"hello"

实践中使用更多的还是加号操作符(+)来拼接字符串。

ECMAScript 还提供了三个基于子字符串创建新字符串的方法:slice()、substr()和substring()。

这三个方法都会返回被操作字符串的一个子字符串,第一个参数指定子字符串的开始位置,slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置。而substr()的第二个参数指定的则是返回的字符个数。

var stringValue = "hello world";
alert(stringValue.slice(3)); //"lo world"
alert(stringValue.substring(3)); //"lo world"
alert(stringValue.substr(3)); //"lo world"
alert(stringValue.slice(3, 7)); //"lo w"
alert(stringValue.substring(3,7)); //"lo w"
alert(stringValue.substr(3, 7)); //"lo worl"

当传入参数是负值时,slice()方法会将传入的负值与字符串的长度相加,substr()方法将负的第一个参数加上字符串的长度,而将负的第二个参数转换为0。最后,substring()方法会把所有负值参数都转换为0。

var stringValue = "hello world";
alert(stringValue.slice(-3)); //"rld"
alert(stringValue.substring(-3)); //"hello world"
alert(stringValue.substr(-3)); //"rld"
alert(stringValue.slice(3, -4)); //"lo w"
alert(stringValue.substring(3, -4)); //"hel"
alert(stringValue.substr(3, -4)); //""(空字符串)

3字符串位置方法

indexOf()和lastIndexOf()。这两个方法都是从一个字符串中搜索给定的子字符串,然后返子字符串的位置(如果没有找到该子字符串,则返回-1)。这两个方法的区别在于:indexOf()方法从字符串的开头向后搜索子字符串,而lastIndexOf()方法是从字符串的末尾向前搜索子字符串。

var stringValue = "hello world";
alert(stringValue.indexOf("o")); //
alert(stringValue.lastIndexOf("o")); //

这两个方法都可以接收可选的第二个参数,表示从字符串中的哪个位置开始搜索。

var stringValue = "hello world";
alert(stringValue.indexOf("o", 6)); //
alert(stringValue.lastIndexOf("o", 6)); //

可以通过循环调用indexOf()或lastIndexOf()来找到所有匹配的子字符串

var stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
var positions = new Array();
var pos = stringValue.indexOf("e");
while(pos > -1){
positions.push(pos);
pos = stringValue.indexOf("e", pos + 1);
}
alert(positions); //"3,24,32,35,52"

4trim()方法

ECMAScript 5 为所有字符串定义了trim()方法。这个方法会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。

var stringValue = " hello world ";
var trimmedStringValue = stringValue.trim();
alert(stringValue); //" hello world "
alert(trimmedStringValue); //"hello world"

由于trim()返回的是字符串的副本,所以原始字符串中的前置及后缀空格会保持不变。支持这个方法的浏览器有IE9+、Firefox 3.5+、Safari 5+、Opera 10.5+和Chrome。此外,Firefox 3.5+、Safari 5+和Chrome 8+还支持非标准的trimLeft()和trimRight()方法,分别用于删除字符串开头和末尾的空格。

5字符串大小写转换方法

ECMAScript 中涉及字符串大小写转换的方法有4 个:toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()。

toLowerCase()和toUpperCase()是两个经典的方法,借鉴自java.lang.String 中的同名方法。而toLocaleLowerCase()和toLocaleUpperCase()方法则是针对特定地区的实现。

var stringValue = "hello world";
alert(stringValue.toLocaleUpperCase()); //"HELLO WORLD"
alert(stringValue.toUpperCase()); //"HELLO WORLD"
alert(stringValue.toLocaleLowerCase()); //"hello world"
alert(stringValue.toLowerCase()); //"hello world"

6字符串的模式匹配方法

7localeCompare()方法

localeCompare()比较两个字符串,并返回下列值中的一个:

如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(大多数情况下是-1,具体的值要视实现而定);

如果字符串等于字符串参数,则返回0;

如果字符串在字母表中应该排在字符串参数之后,则返回一个正数(大多数情况下是1,具体的值同样要视实现而定)。

var stringValue = "yellow";
alert(stringValue.localeCompare("brick")); //
alert(stringValue.localeCompare("yellow")); //
alert(stringValue.localeCompare("zoo")); //-1

因为localeCompare()返回的数值取决于实现,所以最好是像下面例子所示的这样使用这个方法。

function determineOrder(value) {
var result = stringValue.localeCompare(value);
if (result < 0){
alert("The string 'yellow' comes before the string '" + value + "'.");
} else if (result > 0) {
alert("The string 'yellow' comes after the string '" + value + "'.");
} else {
alert("The string 'yellow' is equal to the string '" + value + "'.");
}
}
determineOrder("brick");
determineOrder("yellow");
determineOrder("zoo");

8fromCharCode()方法

fromCharCode()接收一或多个字符编码,然后将它们转换成一个字符串。

alert(String.fromCharCode(104, 101, 108, 108, 111)); //"hello"

9HTML 方法

js高级程序设计(五)引用类型

单体内置对象

ECMA-262 对内置对象的定义是:“由ECMAScript 实现提供的、不依赖于宿主环境的对象,这些对象在ECMAScript 程序执行之前就已经存在了。”意思就是说,开发人员不必显式地实例化内置对象,因为它们已经实例化了。前面我们已经介绍了大多数内置对象,例如Object、Array 和String。ECMA-262 还定义了两个单体内置对象:Global 和Math。

Global对象

ECMAScript 中的Global 对象在某种意义上是作为一个终极的“兜底儿对象”来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是Global 对象的属性。本书前面介绍过的那些函数,诸如isNaN()、isFinite()、parseInt()以及parseFloat(),实际上全都是Global对象的方法。除此之外,Global 对象还包含其他一些方法。

1URI 编码方法

Global 对象的encodeURI()和encodeURIComponent()方法可以对URI(Uniform ResourceIdentifiers,通用资源标识符)进行编码,以便发送给浏览器。有效的URI 中不能包含某些字符,例如空格。而这两个URI 编码方法就可以对URI 进行编码,它们用特殊的UTF-8 编码替换所有无效的字符,从而让浏览器能够接受和理解。

encodeURI()主要用于整个URI(例如,http://www.wrox.com/illegal value.htm),而encode-URIComponent()主要用于对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()编码后的结果是除了空格之外的其他字符都原封不动,只有空格被替换成了%20。而encodeURIComponent()方法则会使用对应的编码替换所有非字母数字字符。这也正是可以对整个URI 使用encodeURI(),而只能对附加在现有URI 后面的字符串使用encodeURIComponent()的原因所在。

与encodeURI()和encodeURIComponent()方法对应的两个方法分别是decodeURI()和decodeURIComponent()。

decodeURI()可将%20 替换成一个空格;decodeURIComponent()可以解码任何特殊字符的编码。

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));

2eval()方法

eval()方法就像是一个完整的ECMAScript 解析器,它只接受一个参数,即要执行的ECMAScript (或JavaScript)字符串。

eval("alert('hi')");

alert("hi");

当解析器发现代码中调用eval()方法时,它会将传入的参数当作实际的ECMAScript 语句来解析,然后把执行结果插入到原位置。通过eval()执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链。这意味着通过eval()执行的代码可以引用在包含环境中定义的变量。

var msg = "hello world";
eval("alert(msg)"); //"hello world"

也可以在eval()调用中定义一个函数,然后再在该调用的外部代码中引用这个函数

eval("function sayHi() { alert('hi'); }");
sayHi();

在eval()中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符串中;它们只在eval()执行的时候创建。

严格模式下,在外部访问不到eval()中创建的任何变量或函数,因此前面两个例子都会导致错误。同样,在严格模式下,为eval 赋值也会导致错误

3Global 对象的属性

js高级程序设计(五)引用类型

ECMAScript 5 明确禁止给undefined、NaN 和Infinity 赋值,这样做即使在非严格模式下也会导致错误。

4window 对象

ECMAScript 虽然没有指出如何直接访问Global 对象,但Web 浏览器都是将这个全局对象作为window 对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,就都成为了window对象的属性。

另一种取得Global 对象的方法是使用以下代码

var global = function(){
  return this;
}();

Math对象

ECMAScript 还为保存数学公式和信息提供了一个公共位置,即Math 对象。

1Math 对象的属性

js高级程序设计(五)引用类型

2min()和max()方法

min()和max()方法用于确定一组数值中的最小值和最大值。这两个方法都可以接收任意多个数值参数

var max = Math.max(3, 54, 32, 16);
alert(max); //
var min = Math.min(3, 54, 32, 16);
alert(min); //

要找到数组中的最大或最小值,可以像下面这样使用apply()方法。

var values = [1, 2, 3, 4, 5, 6, 7, 8];
var max = Math.max.apply(Math, values);

3舍入方法

将小数值舍入为整数的几个方法:Math.ceil()、Math.floor()和Math.round()。这三个方法分别遵循下列舍入规则:

Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;

Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;

Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数(这也是我们在数学课上学到的舍入规则)。

alert(Math.ceil(25.9)); //
alert(Math.ceil(25.5)); //
alert(Math.ceil(25.1)); //
alert(Math.round(25.9)); //
alert(Math.round(25.5)); //
alert(Math.round(25.1)); //
alert(Math.floor(25.9)); //
alert(Math.floor(25.5)); //
alert(Math.floor(25.1)); //

4random()方法

Math.random()方法返回大于等于0 小于1 的一个随机数。

值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)

多数情况下,其实都可以通过一个函数来计算可能值的总数和第一个可能的值

function selectFrom(lowerValue, upperValue) {
var choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}
var num = selectFrom(2, 10);
aler t(num); // 介于 2 和10 之间(包括 2 和 10)的一个数值

5其他方法

js高级程序设计(五)引用类型