《Javascript高级程序设计(第二版)》第五章 引用类型

  引用类型的值(对象)是引用类型的一个实例。引用类型有时候也称为对象定义,对象是某个特定引用类型的实例。

  新对象是使用一个new操作符后面跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。请看下面这行代码:

new person = new Object();

  这行代码创建了引用类型的一个新实例,然后把该实例保存在了变量person中。使用的构造函数是Object(),它只为新对象定义了默认的属性和方法。

5.1 Object类型

  目前,我们看到的大多数的引用类型值都是Object类型的实例。虽然它不具备多少功能,但对于在应用程序中存储和传输数据而言,是非常理想的选择。

  创建object实例的方式有两种:

  1.使用new操作符后跟Object构造函数,如下:

new person = new Object();
person.name = "liuyan";
person.age = 22;

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

var person = {
  name : "liuyan",
  age : 22
}

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

var person = {
  "name" : "liuyan",
  "age" : 22
}

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

var person = {};
person.name = "liuyan";
person.age = 22;

  这个例子与前面的例子是等价的,但一般不推荐这样写。

  虽然前面的任何一种方法都是可行的,但开发人员更青睐对象字面量语法,因为它代码少,还给人一种封装数据的感觉。实际上,对象字面量也是向函数传递大量可选参数的首选方式,例如:

 1 function displayInfo(args){
 2   var output = "";
 3   
 4   if(typeof args.name == "string"){
 5     output += "name: " + args.name + "\n";
 6   }
 7   
 8   if(typeof args.age == "number"){
 9     output += "age: " + args.age + "\n";
10   }
11   
12   alert(output);
13 }
14 
15 displayInfo({
16   name : "liuyan",
17   age : 22
18 });
19 
20 displayInfo({
21   name : "liumin"
22 });

  这种传递参数的模式最适合需要向函数传入大量可选参数的情形。一般来讲,命名参数虽然容易处理,但在多个可选参数的情况下就会显示不够灵活。最好的做法是对那些必需值使用命名参数,而使用对象字面量来封装多个可选参数。

  访问对象属性的方法:

  1.点表示法

alert(person.name);

  2.方括号表示法

alert(person["name"]);

  区别:没有任何区别,但方括号语法的主要优点是可以通过变量来访问属性,例如:

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

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

5.2 Array类型

  创建数组的基本方式:

1 var colors = new Array();
2 var colors = new Array(20);
3 var colors = new Array("red","blue","green");
4 var colors = Array(3);
5 var names = Array("liuyan");

  也可以使用数组字面量表示法:

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

  读取或设置数组的值:

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

  数组的length属性:

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

  数组的length很有特别,他不是只读的。通过设置这个属性,可以从数组的末尾移除或向数组中添加新项,如下:

1 var colors = ["red","blue","green"];  //创建一个包含3个字符串的数组
2 colors.length = 2;
3 alert(colors[2]);   //undefined ,"green"已被移除
4 
5 colors.length = 3;
6 alert(colors[2]);   //undefined ,新增的每一项值都是这个

5.2.1 转换方法

  如前所述,所有对象都具有toLocaleString()toString()valueOf()方法。其中,调用数组的toString()和valueof()方法会返回相同的值,即由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。实际上,为了创建这个字符串会调用数组每一项的toString()方法。如下:

1 var colors = ["red","blue","green"];
2 alert(colors.toString());   //red,blue,green
3 alert(colors.valueOf());    //red,blue,green
4 alert(colors);   //red,blue,green

/**
alert(colors);为什么结果也会是"red,blue,green"呢? 由于alert()要接收字符串参数的,所以它会在后台调用toString()方法,由此会得到与直接调用toString()方法相同的结果。
**/

  另外,toLocaleString()方法经常也会返回与toString()valueOf()方法相同的值,但也不总是如此。当然,也可以使用join()方法使用不同的分隔符来创建字符串。

5.2.2 栈方法

  四个字概括栈的实现:LIFO(last-in-first-out)后进先出。

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

  push()方法可以接受任意数量的参数,把它们逐个加到数组末尾,然后返回数组的长度。

  pop()方法从数组的末尾移除最后一项,减少数组的length值,然后返回移除的项。

 1 var colors = new Array();  //新建一个数组
 2 var count = colors.push("red","green");  //插入两项
 3 alert(count);  //2
 4 
 5 count = colors.push("black");  //插入另一项
 6 alert(count);  //3
 7 
 8 var item = colors.pop();  //取得最后一项
 9 alert(item);  //black
10 alert(colors.length);  //2

5.2.3 队列方法

  四个字概括队列的实现:FIFO(first-in-first-out)先进先出。队列在列表的末尾添加项(可以使用push()),从列表的前端移除项(可以使用shift())。

  shift()方法能够移除数组中的第一项并返回该项,同时将数组长度减1。

 1 var colors = new Array();  //新建一个数组
 2 var count = colors.push("red","green");  //插入两项
 3 alert(count);  //2
 4 
 5 count = colors.push("black");  //插入另一项
 6 alert(count);  //3
 7 
 8 var item = colors.shift();  //取得第一项
 9 alert(item);  //red
10 alert(colors.length);  //2

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

 1 var colors = new Array();  //新建一个数组
 2 var count = colors.push("red","green");  //插入两项
 3 alert(count);  //2
 4 
 5 count = colors.unshift("black");  //插入另一项
 6 alert(count);  //3
 7 
 8 var item = colors.pop();  //取得最后一项
 9 alert(item);  //green
10 alert(colors.length);  //2

  在IE中对javascript有一个偏差,其unshift()方法总是返回undefined而不是数组的新长度。

5.2.4 重排序方法

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

1 var values = [1,2,3,4,5];
2 values.reverse();
3 alert(values);  //5,4,3,2,1
4 
5 var values1 = [0,1,5,10,15];
6 values1.sort();
7 alert(values1);  //0,1,10,15,5

  因为sort()是进行字符串比较的,它会按照ascII排序。所以若想按数字大小排序,还需要添加一个compare()方法。

 1 //由大到小排序
 2 function compare1(value1,value2){
 3   if (value1 < value2){
 4     return 1;
 5   } else if (value1 > value2) {
 6     return -1;
 7   } else {
 8     return 0;
 9   }
10 }
11 
12 var values1 = [0,1,5,10,15];
13 values1.sort(compare1);
14 alert(values1);  //15,10,5,1,0
15 
16 //由小到大排序
17 function compare2(value1,value2){
18   if (value1 < value2){
19     return -1;
20   } else if (value1 > value2) {
21     return 1;
22   } else {
23     return 0;
24   }
25 }
26 
27 var values1 = [0,1,5,10,15];
28 values1.sort(compare2);
29 alert(values1);  //0,1,5,10,15

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

5.2.5 操作方法

  concat()方法会先创建当前数组的一个副本,然后接收到参数添加到这个副本的末尾,最后返回新建的数组。

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

  slice()方法可以接受一或两个参数,起始位置和终止位置,返回起始位置和终止位置的项,如果只有一个参数,默认终止位置为最后一个。(起始位置从0开始,终止位置从1开始)

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

  slice()方法的参数也可以是负数。slice(-2,-1)和slice(3,4)的结果是一样的。

  splice()方法的用途很强大,只要用途是向数组的中部插入项。常用的方法有三种:

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

  插入——可以插入任意项,只需提供三个参数:起始位置、0(要删除的项数)和要插入的任意数量项。例如:splice(2,0,"red","green")会从当前数组的位置2(第三项)开始插入字符串"red"和"green"。

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

  splice()始终会返回一个数组,如果没有被删除的项,则会返回一个空数组。

5.3 Date类型

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

var now = new Date();

  Date()在不传递参数的情况下,新建的对象自动获取当前的日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数。ECMAScript提供了两个方法:Date.parse()Date.UTC()

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

  要创建一个2015年5月25的日期对象,如下:

var someDate = new Date(Date.parse("May 25, 2015"));  //如果字符串不能表示日期,它会返回NaN

  实际上,如直接var someDate = new Date("May 25, 2015")这样,他也会在后台调用Date.parse(),与之前的例子是等价的。

  日期格式化方法和一些组件方法

5.4 RegExp类型

  正则表达式的公式:var expression = / pattern / flags ;

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

  • g——表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
  • i——表示不区分大小写模式;
  • m——表示多行模式,即在到达一行末尾时还会继续查找下一行中是否存在与模式匹配的项。
 1 /*
 2  * 匹配字符串中所有的"at"的实例
 3  */
 4 var pattern1 = /at/g;
 5 
 6 /*
 7  *匹配第一个“bat”或“cat”,不区分大小写
 8  */
 9 var pattern2 = /[bc]at/i;
10 
11 /*
12  *匹配所有以“at”结尾的3个字符的组合,不区分大小写
13  */
14 var pattern3 = /.at/gi

  模式中使用的所有元字符都必须转义。正则表达式中的元字符包括:( [ { \ ^ $ | ) ? * + . ] }

 1 /*
 2  *匹配第一个“[bc]at”,不区分大小写
 3  */
 4 var pattern2 = /\[bc\]at/i;
 5 
 6 
 7 /*
 8  *匹配所有“.at”,不区分大小写
 9  */
10 var pattern2 = /\.at/gi ;

  除了以字面量形式来定义正则表达式,另一种创建正则表达式的方式是使用RegExp构造函数,他接受两个参数:一个是要匹配的字符串模式,另一个是可选的标志字符串。其实跟字面量定义的一样,只是形式不同:

1  /*
2    *匹配第一个“bat”或“cat”,不区分大小写
3    */
4   var pattern1 = /[bc]at/i;
5 
6   var pattern2 = new RegExp("[bc]at","i");

  要注意:传递给RegExp构造函数的两个参数都是字符串(不能把正则表达式字面量传递给RegExp构造函数),所以在某些情况下要对字符串进行双重转义。所有元字符都必须双重转义,那些已经转义过的字符也是如此。例如:\n(字符\在字符串中通常被转移成\\,而在正则表达式字符串中就会变成\\\\)

 5.4.1 RegExp实例属性

  通过这些属性可以取得有关模式的各种信息:

  • global——布尔值,是否设置了g标志
  • ignoreCase——布尔值,是否设置了i标志
  • lastIndex——整数,开始搜索下一个匹配项的字符位置,从0算起
  • multiline——布尔值,是否设置了m标志
  • source——返回字面量形式所用的字符串
 1 var pattern1 = /\[bc\]at/i;
 2 alert(pattern1.global);  //false
 3 alert(pattern1.ignoreCase);  //true
 4 alert(pattern1.multiline);  //false
 5 alert(pattern1.lastIndex);  //0
 6 alert(pattern1.source);  //"\[bc\]at"
 7 
 8 var pattern2 = new RegExp("\\[bc\\]at","i");
 9 alert(pattern1.global);  //false
10 alert(pattern1.ignoreCase);  //true
11 alert(pattern1.multiline);  //false
12 alert(pattern1.lastIndex);  //0
13 alert(pattern1.source);  //"\[bc\]at"

5.4.2 RegExp实例方法

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

1 var text = "mom and dad and baby";
2 var pattern = /mom( and dad( and baby)?)?/gi;
3 
4 var matches = pattern.exec(text);  //["mom and dad and baby", " and dad and baby", " and baby"]
5 alert(matches.index);  //0
6 alert(matches.input);  //"mom and dad and baby"
7 alert(matches[0]);  //"mom and dad and baby"
8 alert(matches[1]);  //"and dad and baby"
9 alert(matches[2]);  //"and baby"

  这个例子包含两个捕获组,分别是"and dad"或者"and dad and baby"。

  正则表达式的第二个方法是test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回true;否则返回false。所以test()方法经常被用到if语句中,如下:

var text = "000-000-000";
var pattern = /\d{3}-\d{3}-\d{3}/;

if(pattern.test(text)){
  alert("the pattern was matched.");
}

5.4.3 RegExp构造函数属性

  下表列出了RegExp构造函数的属性。

5.5 Function类型

  通常的函数使用函数声明语法定义:

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

  使用函数表达式定义函数:

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

  这两种方法其实差不多,但你可能会注意到第二种方法的function后面没有函数名,这是因为在使用函数表达式来定义函数时,没有必要用函数名——通过变量sum即可引用函数。另外,它末尾多了一个分号,因为它是表达式,就像声明变量一样,不要省略了。

  如何理解函数是对象,函数名是指针。如下面的例子:

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

var anotherSum = sum;
alert(anotherSum(10,10));  //20

sum = null;
alert(anotherSum(10,10));  //20

  注意:使用不带圆括号的函数名是访问函数指针,而非调用函数。此时,anotherSum和sum指向同一个函数,一次anotherSum也可以被调用得出结果。即使将sum设置为null,让他与函数“断绝关系”,但仍然可以正常调用anotherSum()。

5.5.1 没有重载

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

5.5.2 函数声明与函数表达式

  现在就对函数声明与函数表达式加以区别。

  实际上,解析器在向执行环境中加载数据时,解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行才会真正被解释执行。看下面的例子:

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

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

  除了这一点,函数声明和函数表达式都是一样的。

5.5.3 作为值的函数

  因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,例如:

function callSomeFunction(someFunction,someArgument){
  return someFunction(someArgument);
}
function add(num){
  return num + 10;  //20
}
var result1 = callSomeFunction(add,10);
alert(result1);
function getGreeting(name){
  return "hello, "+name;
}
var result2 = callSomeFunction(getGreeting,"liuyan");
alert(result2);  //hello, liuyan

  当然,还可以将一个函数作为另一个函数的结果返回。例如:

 

 

 

  

  

posted @ 2015-06-23 11:46  刘牛牛  阅读(102)  评论(0编辑  收藏  举报