JavaScript学习笔记(八)引用类型

引用类型

引用类型的值(对象)是引用类型的一个实例。在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。它也常被称为类,但这种称呼并不妥当。(它不具备传统的面向对象语言所支持的类和接口等基本结构)引用类型有时候也被称为 对象定义 ,因为它们描述的是一类对象所具有的属性和方法。

对象是某个特定引用类型的 实例 。新对象是使用 new 操作符后跟一个构造函数来创建的。
构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。

Object 类型

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

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

另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。下面这个例子就使用了对象字面量语法定义了与前面那个例子中相同的person 对象:

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

在使用对象字面量语法时,属性名也可以使用字符串,如下面这个例子所示。

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

这个例子会创建一个对象,包含三个属性:name、age 和 5。但这里的数值属性名会自动转换为字符串。

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

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

由于"first name"中包含一个空格,所以不能使用点表示法来访问它。然而,属性名中是可以包含非字母非数字的,这时候就可以使用方括号表示法来访问它们。

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

Array类型

ECMAScript 中的数组与其他多数语言中的数组有着相当大的区别。虽然 ECMAScript 数组与其他语言中的数组都是数据的有序列表,但与其他语言不同的是,ECMAScript 数组的每一项可以保存 任何类型 的数据。

而且,ECMAScript 数组的大小是可以 动态调整 的,即可以随着数据的添加自动增长以容纳新增数据。

创建数组的基本方式有 两种

1、使用 Array 构造函数

var colors = new Array(); 

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

var colors = new Array("red", "blue", "green");  // 直接向构造函数传递项

当然,给构造函数传递一个值也可以创建数组。但这时候问题就复杂一点了,因为如果传递的是数值,则会按照该数值创建包含给定项数的数组;而如果传递的是其他类型的参数,则会创建包含那个值的只有一项的数组。下面就两个例子:

var colors = new Array(3); // 创建一个长度为3的数组
var names = new Array("Greg"); // 创建一个长度为1,即字符串"Greg"的数组

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

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

2、使用数组字面量表示法。
数组字面量由一对包含数组项的方括号表示,多个数组项之间以逗号隔开,如下所示:

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

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

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

利用 length 属性也可以方便地在数组末尾添加新项,如下所示:

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

数组最多可以包含 4 294 967 295 个项,这几乎已经能够满足任何编程需求了。如果想添加的项数超过这个上限值,就会发生异常。而创建一个初始大小与这个上限值接近的数组,则可能会导致运行时间超长的脚本错误。

检测数组

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

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

转换方法

有对象都具有 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 

在这里,我们首先显式地调用了 toString()方法,以便返回数组的字符串表示,每个值的字符串表示拼接成了一个字符串,中间以逗号分隔。接着调用 valueOf()方法,而最后一行代码直接将数组传递给了 alert()。由于 alert()要接收字符串参数,所以它会在后台调用 toString()方法,由此会得到与直接调用 toString()方法相同的结果。

另外,toLocaleString()方法经常也会返回与 toString()和 valueOf()方法相同的值,但也不总是如此。当调用数组的 toLocaleString()方法时,它也会创建一个数组值的以逗号分隔的字符串。而与前两个方法唯一的不同之处在于,这一次为了取得每一项的值,调用的 是每一项的 toLocaleString()方法 ,而不是 toString()方法。

如果使用 join()方法,则可以使用 不同的分隔符 来构建这个字符串。join()方法只接收一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串。请看下面的例子:

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

如果不给 join()方法传入任何值,或者给它传入 undefined,则使用 逗号 作为分隔符。

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

栈方法

ECMAScript 数组也提供了一种让数组的行为类似于其他数据结构的方法。具体说来,数组可以表现得就像 一样,后者是一种可以限制插入和删除项的数据结构。

ECMAScript 为数组专门提供了 push()和 pop()方法,以便实现类似栈的行为。

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

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

队列方法

栈数据结构的访问规则是 LIFO(后进先出),而队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出)。队列在列表的末端添加项,从列表的前端移除项。由于 push()是向数组末端添加项的方法,因此要模拟队列只需一个从数组前端取得项的方法。实现这一操作的数组方法就是 shift() ,它能够移除数组中的第一个项并 返回该项 ,同时将数组长度减 1。结合使用 shift()和 push()方法,可以像使用队列一样使用数组。

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

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

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

重排序方法

数组中已经存在两个可以直接用来重排序的方法:

  • reverse() :翻转数组
  • sort() :对数组进行排序

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

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

可见,即使例子中值的顺序没有问题,但 sort()方法也会根据测试字符串的结果改变原来的顺序。因为数值 5 虽然小于 10,但在进行字符串比较时,"10"则位于"5"的前面,于是数组的顺序就被修改了。这种排序方式在很多情况下都不是最佳方案。因此 sort()方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。

比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回 0,如果第一个参数应该位于第二个之后则返回一个正数。以下就是一个简单的比较函数:

function compare(value1, value2) { 
 if (value1 < value2) { 
 	return -1; 
 } else if (value1 > value2) { 
 	return 1; 
 } else { 
 	return 0; 
 } 
} 

这个比较函数可以适用于大多数数据类型,只要将其作为参数传递给 sort()方法即可,如下面这个例子所示。

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

在将比较函数传递到 sort()方法之后,数值仍然保持了正确的升序。当然,也可以通过比较函数产生降序排序的结果,只要交换比较函数返回的值即可。

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); // 15,10,5,1,0 

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

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

由于比较函数通过返回一个小于零、等于零或大于零的值来影响排序结果,因此减法操作就可以适当地处理所有这些情况。

操作方法

concat()方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 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()方法返回从该参数指定位置开始到当前数组 末尾 的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。注意,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 

如果 slice()方法的参数中有一个负数,则用数组长度 加上 该数来确定相应的位置。例如,在一个包含 5 项的数组上调用 slice(-2,-1)与调用 slice(3,4)得到的结果相同。如果结束位置小于起始位置,则返回空数组。

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。在比较第一个参数与数组中的每一项时,会使用全等操作符。

迭代方法

ECMAScript 5 为数组定义了 5 个迭代方法。每个方法都接收两个参数:要在 每一项上运行的函数 和(可选的)运行该函数的 作用域对象 ——影响 this 的值。

传入这些方法中的函数会接收三个参数:数组项的值该项在数组中的位置数组对象本身

  • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true。
  • some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true
  • filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
  • forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。

以上方法都 不会 修改数组中的包含的值。

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){ 
 //执行某些操作 
}); 

归并方法

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

第一次执行回调函数,prev 是 1,cur 是 2。第二次,prev 是 3(1 加 2 的结果),cur 是 3(数组的第三项)。这个过程会持续到把数组中的每一项都访问一遍,最后返回结果。

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


Date类型

要创建一个日期对象,使用 new 操作符和 Date 构造函数即可,如下所示。

var now = new Date(); 

在调用 Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的 毫秒 数(即从 UTC 时间 1970 年 1 月 1 日午夜起至该日期止经过的毫秒数)。

为了简化这一计算过程,ECMAScript 提供了两个方法:Date.parse()和Date.UTC().

Date.parse()方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数。ECMA-262 没有定义 Date.parse()应该支持哪种日期格式,因此这个方法的行为因实现而异,而且通常是因地区而异。

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

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

如果传入 Date.parse()方法的字符串不能表示日期,那么它会返回 NaN 。实际上,如果直接将表示日期的字符串传递给 Date 构造函数,也会在后台调用 Date.parse()。换句话说,下面的代码与前面的例子是等价的:

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

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

// 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()相同。

// 本地时间 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()方法,返回表示调用这个方法时的日期和时间的毫秒数。这个方法简化了使用 Data 对象分析代码的工作。例如:

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

继承的方法

与其他引用类型一样,Date 类型也重写了 toLocaleString()、toString()和 valueOf()方法;但这些方法返回的值与其他类型中的方法不同。

toLocaleString()方法会按照与浏览器设置的地区相适应的格式返回日期和时间。这大致意味着时间格式中会包含 AM 或 PM,但不会包含时区信息(当然,具体的格式会因浏览器而异)。

toString()方法则通常返回带有时区信息的日期和时间,其中时间一般以军用时间(即小时的范围是 0 到 23)表示。

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 日期

以上这些字符串格式方法的输出也是因浏览器而异的,因此没有哪一个方法能够用来在用户界面中显示一致的日期信息。

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 构造函数的模式参数是字符串,所以在某些情况下要对字符进行 双重转义 。所有元字符都必须双重转义,那些已经转义过的字符也是如此,例如\n(字符\在字符串中通常被转义为\ \,而在正则表达式字符串中就会变成\ \ \ \)

字面量模式等价的字符串
/[bc]at/“\[bc\]at”
/.at/“\.at”
/name/age/“name\/age”
/\d.\d{1,2}/“\d.\d{1,2}”
/\w\hello\123/“\w\\hello\\123”

使用正则表达式字面量和使用 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 实例。IE9+、Firefox 4+和 Chrome 都据此做出了修改。

RegExp实例属性

RegExp 的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息。

  • global:布尔值,表示是否设置了 g 标志。
  • ignoreCase:布尔值,表示是否设置了 i 标志。
  • lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从 0 算起。
  • multiline:布尔值,表示是否设置了 m 标志。
  • source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。

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); // 0 
alert(matches.input); // "mom and dad and baby" 
alert(matches[0]); // "mom and dad and baby" 
alert(matches[1]); // " and dad and baby" 
alert(matches[2]); // " and baby"

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

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

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

RegExp构造函数属性

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

模式的局限性

尽管 ECMAScript 中的正则表达式功能还是比较完备的,但仍然缺少某些语言(特别是 Perl)所支持的高级正则表达式特性。

下面列出了 ECMAScript 正则表达式不支持的特性。
在这里插入图片描述

Function类型

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

函数通常是使用函数声明语法定义的,如下面的例子所示。

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

这与下面使用函数表达式定义函数的方式几乎相差无几。

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

以上代码定义了变量 sum 并将其初始化为一个函数。有读者可能会注意到,function 关键字后面没有函数名。这是因为在使用函数表达式定义函数时,没有必要使用函数名——通过变量 sum 即可以引用函数。另外,还要注意函数末尾有一个分号,就像声明其他变量时一样。

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

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

没有重载(深入理解)

将函数名想象为指针,也有助于理解为什么 ECMAScript 中没有函数重载的概念。

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

显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。以上代码实际上与下面的代码没有什么区别。

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

通过观察重写之后的代码,很容易看清楚到底是怎么回事儿——在创建第二个函数时,实际上覆盖了引用第一个函数的变量 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); //20 

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

当然,可以从一个函数中返回另一个函数,而且这也是极为有用的一种技术。例如,假设有一个对象数组,我们想要根据某个对象属性对数组进行排序。而传递给数组 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; 
 		} 
 	}; 
} 

上面这个函数可以像在下面例子中这样使用。

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 的主要用途是保存函数参数,但这个对象还有一个名叫 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" 

上面这个函数 sayColor()是在全局作用域中定义的,它引用了 this 对象。由于在调用函数之前,this 的值并不确定,因此 this 可能会在代码执行过程中引用不同的对象。当在全局作用域中调用sayColor()时,this 引用的是全局对象 window;换句话说,对 this.color 求值会转换成对window.color 求值,于是结果就返回了"red"。而当把这个函数赋给对象 o 并调用 o.sayColor()时,this 引用的是对象 o,因此对 this.color 求值会转换成对 o.color 求值,结果就返回了"blue"。

另一个函数对象的属性caller,这个属性中保存着调用当前函数的 函数的引用,如果是在全局作用域中调用当前函数,它的值为 null。

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

以上代码会导致警告框中显示 outer()函数的源代码。

为了实现更松散的耦合,也可以通过 arguments.callee.caller来访问相同的信息。

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

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

函数属性和方法

函数也有属性和方法。每个函数都包含两个属性:length 和 prototype。其中,length 属性表示函数 希望 接收的命名参数的个数,如下面的例子所示。

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

在 ECMAScript 核心所定义的全部属性中,最耐人寻味的就要数 prototype 属性了。对于ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。换句话说,诸如toString()和 valueOf()等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访问罢了。在创建自定义引用类型以及实现继承时,prototype 属性的作用是极为重要的。在 ECMAScript 5 中,prototype 属性是 不可枚举 的,因此使用 for-in 无法发现。

每个函数都包含两个非继承而来的方法: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)); //20 
alert(callSum2(10,10)); //20 

在严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window。除非明确把函数添加到某个对象或者调用 apply()或 call(),否则 this 值将是undefined

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

事实上,传递参数并非 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())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。在前面例子的第一个版本中,我们是先将 sayColor()函数放到了对象 o 中,然后再通过 o 来调用它的;而在这里重写的例子中,就不需要先前那个多余的步骤了。

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

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

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

基本包装类型

为了便于操作基本类型值,ECMAScript 还提供了 3 个特殊的引用类型:Boolean、Number 和String。这些类型与本章介绍的其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为。实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。

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

这个例子中的变量 s1 包含一个字符串,字符串当然是基本类型值。而下一行调用了 s1 的substring()方法,并将返回的结果保存在了 s2 中。我们知道,基本类型值不是对象,因而从逻辑上讲它们不应该有方法(尽管如我们所愿,它们确实有方法)。其实,为了让我们实现这种直观的操作,后台已经自动完成了一系列的处理。

当第二行代码访问 s1 时,访问过程处于一种读取模式,也就是要从内存中读取这个字符串的值。而在读取模式中访问字符串时,后台都会自动完成下列处理:

  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 

在此,第二行代码试图为字符串 s1 添加一个 color 属性。但是,当第三行代码再次访问 s1 时,其 color 属性不见了。问题的原因就是第二行创建的 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" 

Boolean 类型

Boolean 类型是与布尔值对应的引用类型。要创建 Boolean 对象,可以像下面这样调用 Boolean构造函数并传入 true 或 false 值。

var booleanObject = new Boolean(true); 

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

可是,Boolean 对象在 ECMAScript 中的用处不大,因为它经常会造成人们的误解。其中最常见的问题就是在布尔表达式中使用 Boolean 对象,例如:

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

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

布尔表达式中的所有对象都会被转换为 true,因此 falseObject 对象在布尔表达式中代表的是 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 是与数字值对应的引用类型。要创建 Number 对象,可以在调用 Number 构造函数时向其中传递相应的数值。下面是一个例子。

var numberObject = new Number(10); 

Number 类型也重写了 valueOf()、toLocaleString()和 toString()方法。重写后的 valueOf()方法返回对象表示的基本类型的数值,另外两个方法则返回字符串形式的数值。

除了继承的方法之外,Number 类型还提供了一些用于将数值格式化为字符串的方法。其中,toFixed() 方法会按照指定的小数位返回数值的字符串表示:

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

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

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

另外可用于格式化数值的方法是 toExponential(),该方法返回以指数表示法(也称 e 表示法)表示的数值的字符串形式。与 toFixed()一样,toExponential()也接收一个参数,而且该参数同样也是指定输出结果中的小数位数。看下面的例子。

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

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

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

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

String类型

String 类型是字符串的对象包装类型,可以像下面这样使用 String 构造函数来创建。

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

tring 对象的方法也可以在所有基本的字符串值中访问到。其中,继承的valueOf()、toLocaleString()和 toString()方法,都返回对象所表示的基本字符串值。String 类型的每个实例都有一个 length 属性,表示字符串中包含多少个字符。

即使字符串中包含双字节字符(不是占一个字节的 ASCII 字符),每个字符也仍然算一个字符。

1、字符方法

两个用于访问字符串中特定字符的方法是:charAt()和 charCodeAt()。这两个方法都接收一个参数,即基于 0 的字符位置。

charAt()方法以单字符字符串的形式返回给定位置的那个字符(ECMAScript 中没有字符类型)。

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

如果你想得到的不是字符而是字符编码,那么就要像下面这样使用 charCodeAt()了。

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

这个例子输出的是"101",也就是小写字母"e"的字符编码。

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

var stringValue = "hello world"; 
alert(stringValue[1]); //"e"
2、字符串操作方法

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

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

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

ECMAScript还提供了三个基于子字符串创建新字符串的方法:slice()substr()substring()。这三个方法都会返回被操作字符串的一个子字符串,而且也都接受一或两个参数。第一个参数指定子字符串的 开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束。

slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的 位置 。而 substr()的第二个参数指定的则是返回的 字符个数

如果没有给这些方法传递第二个参数,则将字符串的长度作为结束位置。与concat()方法一样,slice()、substr()和 substring()也 不会 修改字符串本身的值——它们只是返回一个基本类型的字符串值,对原始字符串没有任何影响。

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)); //""(空字符串)

substring()方法在使用的时候如果第二个位置的值比第一个位置的值小,那么它会把较小的值放到前面,即substring(3, 0)相当于substring(0, 3).

3、字符串位置方法

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

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

这两个方法都可以接收可选的第二个参数,表示从字符串中的哪个位置开始搜索。换句话说,indexOf()会从该参数指定的位置向后搜索,忽略该位置之前的所有字符;而 lastIndexOf()则会从指定的位置向前搜索,忽略该位置之后的所有字符。看下面的例子。

var stringValue = "hello world"; 
alert(stringValue.indexOf("o", 6)); //7 
alert(stringValue.lastIndexOf("o", 6)); //4 
4、trim()方法

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()方法则是针对特定地区的实现。对有些地区来说,针对地区的方法与其通用方法得到的结果相同,但少数语言(如土耳其语)会为 Unicode 大小写转换应用特殊的规则,这时候就必须使用针对地区的方法来保证实现正确的转换。以下是几个例子。

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、字符串的模式匹配方法

String 类型定义了几个用于在字符串中匹配模式的方法。第一个方法就是 match(),在字符串上调用这个方法,本质上与调用 RegExp 的 exec()方法相同。match()方法只接受一个参数,要么是一个正则表达式,要么是一个 RegExp 对象。来看下面的例子。

var text = "cat, bat, sat, fat"; 
var pattern = /.at/; 
//与 pattern.exec(text)相同
var matches = text.match(pattern); 
alert(matches.index); //0 
alert(matches[0]); //"cat" 
alert(pattern.lastIndex); //0 

本例中的 match()方法返回了一个数组;如果是调用 RegExp 对象的 exec()方法并传递本例中的字符串作为参数,那么也会得到与此相同的数组:数组的第一项是与整个模式匹配的字符串,之后的每一项(如果有)保存着与正则表达式中的捕获组匹配的字符串。

另一个用于查找模式的方法是 search()。这个方法的唯一参数与 match()方法的参数相同:由字符串或 RegExp 对象指定的一个正则表达式。search()方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1。而且,search()方法始终是从字符串开头向后查找模式。看下面的例子。

var text = "cat, bat, sat, fat"; 
var pos = text.search(/at/); 
alert(pos); //1 

为了简化替换子字符串的操作,ECMAScript 提供了 replace()方法。这个方法接受两个参数:第一个参数可以是一个 RegExp 对象或者一个字符串(这个字符串不会被转换成正则表达式),第二个参数可以是一个字符串或者一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局(g)标志,如下所示。

var text = "cat, bat, sat, fat"; 
var result = text.replace("at", "ond"); 
alert(result); //"cond, bat, sat, fat" 
result = text.replace(/at/g, "ond"); 
alert(result); //"cond, bond, sond, fond" 

在这里插入图片描述
通过这些特殊的字符序列,可以使用最近一次匹配结果中的内容,如下面的例子所示。

var text = "cat, bat, sat, fat"; 
result = text.replace(/(.at)/g, "word ($1)"); 
alert(result); //word (cat), word (bat), word (sat), word (fat) 

replace()方法的第二个参数也可以是一个函数。在只有一个匹配项(即与模式匹配的字符串)的情况下,会向这个函数传递 3 个参数: 模式的匹配项模式匹配项在字符串中的位置原始字符串。在正则表达式中定义了多个捕获组的情况下,传递给函数的参数依次是模式的匹配项、第一个捕获组的匹配项、第二个捕获组的匹配项……,但最后两个参数仍然分别是模式的匹配项在字符串中的位置和原始字符串。这个函数应该返回一个字符串,表示应该被替换的匹配项使用函数作为 replace()方法的第二个参数可以实现更加精细的替换操作,请看下面这个例子。

function htmlEscape(text){ 
 	return text.replace(/[<>"&]/g, function(match, pos, originalText){ 
 		switch(match){ 
 			case "<": 
 				return "&lt;"; 
			case ">": 
 				return "&gt;"; 
 			case "&": 
 				return "&amp;"; 
 			case "\"": 
 				return "&quot;"; 
 		} 
 	}); 
} 
alert(htmlEscape("<p class=\"greeting\">Hello world!</p>")); 
//&lt;p class=&quot;greeting&quot;&gt;Hello world!&lt;/p&gt; 

最后一个与模式匹配有关的方法是 split(),这个方法可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个 RegExp 对象(这个方法不会将字符串看成正则表达式)。split()方法可以接受可选的第二个参数,用于指定 数组的大小,以便确保返回的数组不会超过既定大小。请看下面的例子。

var colorText = "red,blue,green,yellow"; 
var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"] 
var colors2 = colorText.split(",", 2); //["red", "blue"] 
var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""] 
7、localeCompare()方法

与操作字符串有关的最后一个方法是 localeCompare(),这个方法比较两个字符串,并返回下列值中的一个:

  • 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(大多数情况下是-1,具体的值要视实现而定);
  • 如果字符串等于字符串参数,则返回 0;
  • 如果字符串在字母表中应该排在字符串参数之后,则返回一个正数(大多数情况下是 1,具体的值同样要视实现而定)
var stringValue = "yellow"; 
alert(stringValue.localeCompare("brick")); //1 
alert(stringValue.localeCompare("yellow")); //0 
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"); 

localeCompare()方法比较与众不同的地方,就是实现所支持的地区(国家和语言)决定了这个方法的行为。比如,美国以英语作为 ECMAScript 实现的标准语言,因此 localeCompare()就是区分大小写的,于是大写字母在字母表中排在小写字母前头就成为了一项决定性的比较规则。不过,在其他地区恐怕就不是这种情况了。

8. fromCharCode()方法

另外,String 构造函数本身还有一个静态方法:fromCharCode()。这个方法的任务是接收一或多个字符编码,然后将它们转换成一个字符串。从本质上来看,这个方法与实例方法 charCodeAt()执行的是相反的操作。来看一个例子:

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

单体内置对象

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

Global对象

Global(全局)对象可以说是 ECMAScript 中最特别的一个对象了,因为不管你从什么角度上看,这个对象都是不存在的。ECMAScript 中的 Global 对象在某种意义上是作为一个终极的“兜底儿对象”来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是 Global 对象的属性

诸如 isNaN()isFinite()parseInt() 以及 parseFloat(),实际上全都是 Global对象的方法。除此之外,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));

一般来说,我们使用 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)); 

URI 方法 encodeURI()、encodeURIComponent()、decodeURI()和 decodeURIComponent()用于替代已经被ECMA-262第3版废弃的escape()和unescape()方法。URI方法能够编码所有 Unicode字符,而原来的方法只能正确地编码 ASCII 字符。因此在开发实践中,特别是在产品级的代码中,一定要使用 URI方法,不要使用 escape()和 unescape()方法。

2、eval()方法

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("var msg = 'hello world'; "); 
alert(msg); //"hello world" 

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

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

"use strict"; 
eval = "hi"; //causes error 
3、global对象的属性

在这里插入图片描述

4、window对象

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

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

这里定义了一个名为color的全局变量和一个名为sayColor()的全局函数。在sayColor()内部,我们通过 window.color 来访问 color 变量,以说明全局变量是 window 对象的属性。然后,又使用window.sayColor()来直接通过 window 对象调用这个函数,结果显示在了警告框中。

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

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

以上代码创建了一个立即调用的函数表达式,返回 this 的值。如前所述,在没有给函数明确指定this 值的情况下(无论是通过将函数添加为对象的方法,还是通过调用 call()或 apply()),this值等于 Global 对象。而像这样通过简单地返回 this 来取得 Global 对象,在任何执行环境下都是可行的。

Math对象

1、Math对象的属性

在这里插入图片描述

2、min()和max()方法

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 值。

3、舍入方法

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

  • 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); 
5、其他方法

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值