JavaScript读书笔记06

引用类型

回顾下之前关于引用类型的概念,引用类型的值叫做该引用类型的一个实例,也是一个对象。在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起,跟其他的面向对象语言的类是有一定的差别的。

要创建一个新的对象,可以用new操作符跟上构造函数来创建:

var person = new Object()

再谈Object类型

目前为止,我们看到的引用类型都是Object类型,虽然Object类型不具备很多的功能,但对于存储和传输数据而言,Object确实非常合适,因为其动态属性的特征:

var person = new Object();
person.name = "zhangsan";
person.age = 18;

对象字面量

这里我们为person增加了两个属性,但当属性多起来的时候,这样的写法会显得很麻烦,所以还有另外一种使用对象字面量的表示法,对象字面量是对象定义的一种简写形式:

var person = {
	name : "zhangsan",
	age : 18
};

两种形式的效果是一样的。大括号包围着的属性对就是对象字面量的表示法。在使用字面量表示法时,属性名也可以用字符串来表示:

var person = {
"name" : "zhangsan",
"age" : 18,
5 : true
}

这个例子创建了一个对象包含了三个属性,name, age5,这里的5会自动转成字符串。

当然你可以在对象字面量中不包含任何的属性对,这样就只包含了默认属性和方法:

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

属性访问

在ECMAScript中,访问对象的属性就是通过点表示法,这与很多面向对象的语法保持一致。不过ECMAScript中也可以使用方括号表示法来访问属性,使用方括号时应该讲属性以字符串的形式放到方括号内:

alert(person.name);			//点表示法
alert(person["name"]); //方括号表示法

从功能上看这两种表示法没有区别,但是在以下两种情况下只能用中括号表示法:

  • 通过变量访问属性
  • 属性名不符合标识符的规则

分别举个例子:

var propertyName = "name";
alert(person[propertyName]); //通过变量访问属性

alert(person["5"]); //5不符合标识符的规则
alert(person["first name"]); //first name也不符合标识符的规则

通常,除了上面两种情况必须用方括号表示,否则我们建议用点表示法

Array类型

除了Object类型之外,Array类型恐怕是ECMAScript中最常用的类型了。在ECMAScript中的数组跟其他语言数组也是有一定区别的,主要是以下两点区别:

  • 数组的每一项都可以保存任何类型的数据;
  • 数组的大小是可以动态调整的。

创建数组

创建数组也有两种方法,使用Array构造函数和使用数组字面量:

var colors = new Array();			//创建一个空数组
var colors = new Array(5); //创建一个包含5项的数组
var colors = new Array("red", "blue", "green"); //创建一个包含三个字符串的数组

var colors = []; //创建一个空数组
var colors = [,,,,]; //不要这样,穿件一个包含4项或者5项的数组
var colors = ["red", "blue", "green"]; //创建一恶搞包含三个字符串的数组

使用new Array()时,可以省略掉new操作符。前文也提到过没有参数的时候也可以省略掉括号,但是不推荐这样做。而且两者不能同时被省略掉,这样不是在产生新的对象,而是把类型给赋值了。

上面的代码很简单,但是有几点需要注意,首先是Array构造函数接受参数的时候有时候当成是数组的长度,有时候当成是数组的内容,如何区分呢?很简单,当只有一个参数时,如果参数是数值,给定项数的数组,如果是其他类型的值就创建包含这一项的数组:

var colors = new Array(3);			//创建包含3项的数组
var colors = new Array("red"); //创建包含“red”的数组

那么一个问题就来了,我就是想要创建包含数值3的数组该怎么办?这样或许就是更好的办法:

var colors = [3];				//创建一个包含数值3的数组

两种穿件数组的方式个有特点,所以选择合适的就行。比如要创建给定项数的数组就用构造函数方法更加合理,因为字面量的方法会在不同的浏览器中给出不同的解释,所以这样不推荐使用。

最后,如果数组中的元素还没初始化,那么默认的值就是undefined

访问数组

ECMAScript中访问数组的方式也是跟其他语言的语法保持了一致,采用中括号和基于0的索引:

var colors = ["red", "green", "blue"];
alert(colors[0]); //"red"
colors[2] = "black"; //修改第三项
colors[3] = "yellow"; //增加第四项

例子中可以看到,访问越界都没关系,这就是上面提到的区别之二,数组长度是动态调整的。

数组的项数保存在length属性当中,这个属性始终会返回0或者更大的值。并且它不是只读的,刚已经提到了,ECMAScript的数组长度是动态调整的,所以你可以设定长度:

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

colors.length = 2; //移除了最后一项
alert(colors[2]); //undefined

colors.length = 5; //增加了新的三项
alert(colors[2]); //undefined,新增项默认值

利用数组的动态大小的特性,在数组最后增加元素就可以很轻松的完成:

colors[colors.length] = "black";
colors[colors.length] = "yellow";

检测数组

检测某个对象是不是数组,可以用instanceof来解决:

if(value instanceof Array){
//doSomeThingWithArray
}

但这里存在一个问题是instanceof操作符嘉定只有一个全局执行唤醒。如果网页中包含多个框架,实际上就存在两个以上的不同全局执行环境,从而出现两个以上的不同版本的Array的构造函数。如果从一个框架传递一个数组到另外一个框架,那么传入的数组跟第二个框架中的原生穿件的数组分别具有各自不同的构造函数。为了解决这个问题,ECMAScript 5新增了Array.isArray()方法,专门来检查某个值是不是数组,而不管它在哪个全局执行环境中创建:

if(Array.isArray(value)){
//doSomeThingWithArray
}

转换方法

如前所述,所有对象都会toLocaleString(), toString()valueOf()方法,数组当然也会有,值分别为:

  • toString(): 分别调用每一项的toString(), 然后以逗号为分隔符拼接成字符串;
  • toLocaleString(): 同上,调用的是每一项的toLocaleString();
  • valueOf: 返回该数组本身。

如果需要以其他作为分隔符的字符串表示,那么可以用join()方法,接受一个参数,作为分隔符:

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

栈方法

pop() push()

ECMAScript中数组也一组让数组行为类似于栈的方法,就是push()pop(),它们操作的栈顶就是数组的尾部,并且push返回push之后数组的长度,pop返回移除的最后一项:

var colors = ["red", "green", "blue"];
var count = colors.push("black", "yellow");
alert(count); //5

var item = colors.pop();
alert(item); //yellow
alert(colors.length()); //4

队列方法

shift()

既然数组都模拟栈的行为了,总不能少了队列,而且push()已经完成了从数组末端添加项的方法,现在就差从对头获取元素的方法了,这就是shift(), 它移除数组中的第一个项并且返回该项:

var colors = ["red", "green", "blue"];
colors.push("black");
var item = colors.shift();
alert(item); //red
alert(colors.length()); //3
unshift()

同时ECMAScript也提供了unshift()的方法,它实现的效果完全相反,在数组前端添加任意个项,并且返回新的数组的长度。因此unshift()pop()一起,可以实现反向的队列。

重排序方法

数组中有两个可以用来排序的方法:reverse(), sort()

reverse()

reverse()方法反转数组中的项的顺序:

var values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); //5,4,3,2,1
sort()

没有参数的情况下,sort()函数按升序排列数组,调用每一项的toString()转型方法,然后比较字符串,确定排序。即使数组中全部是数值,也会比较字符串的大小:

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

字符串比较时,“5”是比“10”大的,所以拍到了后面。不用说,这不是我们想要的结果,所以sort()也接受一个比较函数作为参数。比较函数接受两个参数:

  • 如果第一个参数应该位于第二个参数之前,返回负数;
  • 如果相等,返回0;
  • 如果第一个参数应该位于第二个参数之后,返回正数。

所以一个升序的比较函数则是:

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

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

对于数值类型或者其valueOf()方法返回数值类型的对象类型,可以使用更简单的比较函数:

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

操作方法

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

切片操作可以基于当前数组中的一项或者多项创建一个新的数组。接受两个参数,分别是要返回项的起始位置和终止位置,返回不包含终止位置的项。第二个参数可以省略,如果省略就返回从起始位置一直到数组末尾的所有项。切片也不会影响当前的数组。

var colors = ["red", "green", "blue", "black", "brown"];
var colors2 = colors.slice(2);
var colors3 = colors.slice(2,4);

alert(colors2); //blue,black,brown
alert(colors3); //blue,black

如果slice()方法参数中有负数,则用数组长度加上该数来确定相应的位置。

splice()

功能强大的数组方法,主要的用途是可以从数组中删除一些项,再插入一些项,然后返回由删掉的项组成的数组。第一个参数是操作开始的位置,第二个参数是删除的项的个数,第三个以及之后的参数是插入的项。如果不需要插入项直接省略第三个参数,如果不需要删除项那么第二个参数就指定为0:


var colors = ["red", "green", "blue"];

var removed = colors.splice(1,1); //只删除
alert(removed); //green
alert(colors); //red,blue

removed = colors.splice(1,0,"black", "brown"); //只插入
alert(removed); //空数组[]
alert(colors); //red,black,brown,blue

removed = colors.splice(2,2,"purplr"); //插入同时删除
alert(removed); //brown,blue
alert(colors); //red,black,purple

位置方法

indexOf() lastIndexOf()

这两个位置方法都接受两个参数,第一个是要查找的项,第二个参数可选,是查找的起点位置的索引。indexOf()从开始向后找, lastIndexOf()从末尾开始向前找:

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

alert(numbers.indexOf(4)); //3
alert(numbers.lastIndexOf(4)); //5

alert(numbers.indexOf(4, 4)); //5
alert(numberslastIndexOf(4, 4)); //3

查找失败都会返回-1,而且在比较第一个参数和每一个项是用的是全等操作符,要求查找的项必须严格相等(===):


var person = {name : "zhangsan"};
var people = [{name : "zhangsan"}];

var morePeople = [person];

alert(people.indexOf(person)); //-1
alert(morePeople.indexOf(person)); //0

迭代方法

ECMAScript 5为数组定义了5个迭代方法。每个方法都接受两个参数:第一个是要在每一项上运行的函数,第二个参数是可选的,有运行该函数的作用域对象。而第一个参数需要的函数会接受到3个参数:该项的值,该项在数组中的位置和数组对象本身。

  • every():对数组每一项运行给定函数,全部为true则返回true否则返回false
  • fliter():对数组每一项运行给定函数,返回true的项组成新的数组,返回这个数组
  • forEach():对数组每一项运行给定函数,没有返回值
  • map():对数组每一项运行给定函数,返回调用结果组成的数组
  • some():对数组每一项运行给定函数,只要一个为true就返回true

var largerThanTen = function(num){
console.log(arguments)
return num > 10;
}

var numbers = [3,20,8,39,200,5];

var everyRes = numbers.every(largerThanTen); //false
var someRes = numbers.some(largerThanTen); //true

var fliterRes = numbers.fliter(largerThanTen); //[20, 39, 200]
var mapRes = numbers.map(largerThanTen); //[false, true, false, true, true, false]

numbers.forEach(function(item, index, array){
//doSomething
})

归并方法

reduce() reduceRight()

这两个归并方法都会迭代数组的所有项,构建一个最后返回值,reduce()从数组的第一项开始往后遍历,而reduceRight()则从最后一项开始,向前遍历到第一项。

这两个方法都接受两个参数,在每一项上调用的函数和归并的初始值(可选)。调用的函数会接受到4个参数,前一个值、当前值、项的索引和数组对象。这个函数返回的任何职都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,而第一个参数就是数组的第一项。

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

alert(sum); //15