JavaScript高级程序设计:第五章

引用类型

一、object类型:

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

var  person = new  Object();

person.name = “Nicholas”;

person.age = 29;

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

person 对象:

var  person = {

name:“Nicholas”,

age:29

};

在这个例子中,左边的花括号表示对象字面量的开始,因为它出现在了表达式上下文中。我们定义了name属性,之后是一个冒号,再后面是这个属性的值。在对象字面量中,使用逗号来分隔不同的属性,因此“Nicholas”后面是一个逗号。但是,在age属性的值29后面不能添加逗号,因为age是这个对象的最后一个属性。

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

var  person = {

“name”:“Nicholas”,

“age”:  29 ,

5 : true

};

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

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

var  person = { } ;

person.name = “Nicholas”;

person.age = 29 ;

对象字面量方法更受青睐,实际上,对象字面量也是向函数传递大量可选参数的首选方式。

一般来说,访问对象属性有两种方法:点表示法和方括号语法。如下面的例子:

alert(person[“name”]);          //“Nicholas”

alert(person.name);               //“Nicholas”

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

var  propertyName = “name”;

alert(person[propertyName]);      //“Nicholas”

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

person[“first  name”] = “Nicholas” ;

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

二、Array类型

创建数组的基本方式有两种。第一种是使用Array构造函数,如下面的代码所示:

var  colors = new  Array();

如果预先知道数组要保存的项目数量,也可以给构造函数传递该数量,而该数量会自动变成length属性的值。例如:

var  colors = new  Array(20);

1.检测数组:检测某个对象是不是数组使用instanceof操作符:

if ( value  instanceof  Array){

//对数组执行某些操作

}

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

if ( Array . isArray ( value )){

//对数组执行某些操作

}

2.转换方法:

调用数组的toString()方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。而调用valueOf()返回的还是数组。实际上,为了创建这个字符串会调用数组每一项的toString()方法。另外,toLocaleString()方法经常也会返回与toString()和valueOf()方法相同的值,但也不总是如此。当调用数组的toLocaleString()方法时,它也会创建一个数组值的以逗号分隔的字符串。而与前两个方法唯一的不同之处在于,这一次为了取得每一项的值,调用的是每一项的toLocaleString()方法,而不是toString()方法。请看下面的例子:

var  person1 = {

toLocaleString : function () {

return “Nikolaos” ;

} ,

toString : function () {

return “ Nicholas” ;

}

} ;

var  person2 = {

toLocaleString : function () {

return “Grigorios” ;

} ,

toString : function () {

return : “Gerg” ;

}

} ;

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

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

3.栈方法:

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

var  item = colors . pop ( ) ;                   //取得最后一项

alert ( item ) ;         //”black”

alert ( colors . length ) ;       //2

4.队列方法:

队列在列表的末端添加项,从列表的前端移除项。由于push ( )是向数组末端添加项的方法,因此要模拟队列只需一个从数组前端取得项的方法。实现这一操作的数组方法就是shift()方法,它能够移除数组中的第一个项并返回该顶项,同时将数组长度减1。结合使用shift()和push()方法,可以像使用队列一样使用数组。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

5.重排序方法

数组中已经存在的两个可以直接用来重排序的方法:reverse()和sort()。reverse()方法会对反转数组项的顺序。

例如:

var  value = [ 1 , 2 , 3 , 4 , 5 ] ;

values.reverse() ;

alert(values) ;                //5 , 4 , 3 , 2 , 1

这个方法的作用相当直观明了,但不够灵活,因此才有了sort()方法。

在默认情况下,sort()方法按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。

6.操作方法:

(1)ECMAScript为操作已经包含在数组中的项提供了很多的方法。其中,concat()方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组的一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回重新构建的数组。在没有给concat()方法传递参数的情况下,它只是复制当前数组并返回副本。如果传递给concat()方法的是一个或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。下面来看一个例子:

var  colors = [ “red” , “green” , “blue” ] ;

var  colors2 = colors.concat (“yellow” , [“black” , “brown”] ) ;

alert( colors ) ;            //red , green , blue

alert( color2 ) ;            //red , green , blue , yellow , black , brown

(2)下一个方法是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

(3)接下来介绍splice()方法,这个方法恐怕要算是最强大的数组方法了,它有很多种用法。splice()的主要用途是向数组的中部插入项,但使用这种方法的方式有如下3种。

删除:可以删除任意数量的项,只需指定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 , orangle , blue

alert( removed ) ;           //返回的是一个空数组

removed = colors . splice( 1 , 1 , “red” , “purple”) ;    //插入两项,删除一项

alert( colors ) ;               //green ,red ,purple ,orange ,blue

alert( removed ) ;            //yellow,返回的数组中只包含一项

7.位置方法:

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

两个方法都要返回要查找的项在数组中的位置,或者在没找到的情况下返回-1。在比较第一个参数与数组中的每一项时,会使用全等操作符;也就是说,要求查找的项必须严格相等。

8.迭代方法:

ECMAScript5为数组定义了5个迭代的方法。每个方法都接收两个参数:要在每一项上运行的函数(可选)和运行该函数的作用域对象——影响this的值。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。根据使用的方法不同,这个函数执行后的返回值可能会也可能不会影响访问的返回值。以下是这5个迭代方法的作用:

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

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

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

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

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

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

9.缩小方法:

ECMAScript5还新增了两个缩小数组的方法:reduce()和reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中,reduce()方法从数组的第一项开始,逐个遍历到最后。而reduceRight()则从数组的最后一项开始,向前遍历到第一项。这两个方法都接收两个参数:一个在每一项上调用的函数和(可选)作为缩小基础的初始值。传给reduce()和reduceRight()的函数接收4个参数:前一个值、当前值、项的索引和数组对象。

三、date类型

ECMAScript中的Date类型是在早期Java中的java.util.Date类基础上构建的。要创建一个日期对象,使用new操作符和Date构造函数即可,如下所示。

var  now = new Date();

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

其中,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)。

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

var  someDate = New Date (Date.parse(“May  25 , 2004”));

如果传入Date.parse()方法的字符串不能表示日期,那么它会返回NaN。

1.继承的方法:

Date类型的toLocaleString()方法会按照浏览器设置的地区相适应的格式返回日期和时间。这大致意味着时间格式中会包含AM或PM,但不会包含失去信息。而toString()方法则通常返回带有时区信息的日期和时间,其中时间一般以军用时间表示。下面给出了在不同浏览器中调用toLocaleString()和toString()方法,输出PST时间2007年2月1日午夜零时的结果。至于Date类型的valueOf()方法,则根本不返回字符串,而是返回日期的毫秒表示。因此,可以方便使用比较操作符(大于或小于)来比较日期值。请看下面的例子:

var  date1 = new Date(2007 ,0 ,1 ) ;

var  date2 = new Date(2007 ,1 ,1 ) ;

alert(date1 < date2) ;    //true

alert( date1 > date2) ;    //false

2.日期格式化方法:

Date类型还有一些专门用于将日期格式化为字符串的方法,这些方法如下:

toDateString()——以特定于实现的格式显示星期几、月、日和年;

toTimeString()——以特定于实现的格式显示时、分、秒和时区;

toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;

toUTCString()——以特定于实现的格式完整的UTC日期。

3.日期/时间组件方法“

UTC日期指的是在没有时区偏差的情况下(将日期转换为GMT时间)的日期值。

4.RegExp类型

ECMAScript通过RegExp类型来支持正则表达式。使用下面类似Perl的语法,就可以创建一个正则表达式。

var  expression = /  patten  /  flags ;

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

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

i:表示不区分大小写模式,即在确定匹配项时忽略模式与字符串的大小写;

m:表示多行模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。

因此,一个正则表达式就是一个模式与上述3个标志的组合体。

(1)RegExp实例属性

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

global:布尔值,表示是否设置了g标志。

ignoreCase:布尔值,表示是否设置了i标志。

laseIndex:整数,表示开始搜索下一个匹配项的字符位置,从0算起。

multiline:布尔值,表示是否设置了m标志。

source:正则表达式的字符串表示,按字面量形式而非传入构造函数的字符串模式返回。

(2)RegExp实例:

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

(3)RegExp构造函数属性

RegExp构造函数包含一些属性。这些属性适用于作用域中的所有正则表达式,并且基于所执行的最近一次正则表达式操作而变化。RegExp构造函数的各个属性返回了下列值:

input属性返回了原始字符串;

leftContext属性返回了单词short之前的字符串,而rightContext属性则返回了short之后的字符串;

lastMatch属性返回最近一次与整个正则表达式匹配的字符串,即short;

lastParen属性返回最近一次匹配的捕获组,即例子中的s。

五、function类型:

函数实际上是对象,而每个函数都是function类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函数声明语法定义的,如下面的例子所示:

function  sum (sum1 ,sum2){

return  num1+num2 ;

}

var  sum = function(num1 ,num2){

return  num1+num2 ;

}

(1)没有重载

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

function  addSomeNumber( num ) {

return  num+100 ;

}

function  addSomeNumber( num ) {

return  num+200 ;

}

这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。

(2)函数声明与函数表达式

解析器会率先读取函数声明,并使其在执行任何代码之前可用,至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。如下例子:

alert(sum(10 ,100));

function  sum(num1 ,num2){

return  num1+num2 ;

}

以上代码完全可以正常运行,因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中。

(3)作为值的函数

函数也可以作为值来使用,也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。来看一看下面的函数:

function  callSomeFunction(someFunction ,someArgument) {

return  someFunction(someArgument);

}

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

(4)函数内部属性:

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

(5)函数的属性和方法

每个函数都包含两个属性: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

以上代码定义了3个函数,每个函数接收的命名参数个数不同。首先,sayName()函数定义了一个参数,因此length属性值为1。类似的,sum()函数定义了两个参数,结果其length属性中保存的值为2。而sayHi()没有命名参数,所以其length值为0。

对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。

六、基本包装类型

1.为了便于操作基本类型值,ECMAScript还提供了3个特殊的引用类型:Boolean、Number、String。这些类型与其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为。引用类型与基本包装类型的主要却别就是对象的生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则值存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能再运行时为基本类型值添加属性和方法。

上一篇:React源码解析之React.Children.map()(五)


下一篇:纯CSS3写的10个不同的酷炫图片遮罩层效果