引用类型-Array
什么是数组
数组是数据的有序列表,与其他语言不同的是,ECMAScript中的数组每一项可以保存任何类型的数据结构。比如第一项可以保存字符串类型,第二项可以保存数字类型,第三项可以保存布尔值类型,第四项可以保存对象,甚至第五项可以保存数组。ECMAScript中的数组的大小是根据数据的增加而动态调整的。
Array构造函数的作用就是用来来创建数组
var array = new Array('string', 12, true, {name: 'liuxu'}, [1,2,3]); //这里使用构造函数的方式创建了包含不同数据类型的数组
数组的创建方式
使用构造函数创建数组,会根据传入参数的不同而导致创建出不同的数组
var colors = new Array(); //如果不传递参数则创建一个空数组 var colors = new Array(3); //如果传递的是一个数字,则会创建一个3项的数组,其中每一项的指是undefined。 var colors = new Array(0.5); var colors = new Array(-1); //使用数值的情况,如果传递的是负值或者非整数,会出现错误 var colors = new Array('yellow') //如果传递是字符串则会创建包含一项,之为yellow的字符串。其他的数据类型也是如此。
在使用构造函数创建不包含内容,只包含项数的情况,即传递数值的情况需要注意
var colors = new Array(3); alert(colors.length); //这里包含三项,所以length为3 alert(colors[0]); //每一项自动获取undefined为值 alert(typeof colors[0]); //每一项的值是undefined类型的特殊值undefined,并非是undefined字符串。 var obj = new Object(); obj.name = 'liuhaixiao'; alert(('name' in obj)); //通过for-in如果这个属性存在对象中无论是原型还是自身都会返回true alert(('0' in colors)); //但是对于这样创建的数组没有键名。
创建数组的时候可以将构造函数当作普通函数来使用,即不使用new操作符来调用构造函数。
var colors = new Array('yellow'); var colors = Array('yellow'); //这两种方式没有什么不同
使用字面量的方式创建数组,和Object一样,如果使用字面量的方式创建数组则不会调用相应的构造函数。建议使用这种方式。
var colors = ['yellow', 'blue', 'green']; var colors = ['yellow', 'blue', 'green',]; //两个数组看起来相同,但是第二个数组后面多了一个逗号,这在高版本浏览器中没什么问题,但是在ie8之前的浏览器中,会认为逗号后面还有一项数据,这个数据没有内容自动获得undefined,所以不要在最后一项添加逗号。
检测数组
typeof运算符可以检测变量的数据类型,但是对于数组来说,会返回object,并没有办法确定这个对象是数组
var colors = ['yellow']; alert(typeof colors); //object
instanceof运算符可以检测一个对象在原型链上能够访问的构造函数或者说继承的构造函数
function Preson(name, age){ this.name = name; this.age = 12; } //这里创建一个构造函数 var preson1 = new Preson('liuhaixiao', 17); //实例化一个对象 alert(preson1 instanceof Preson); //本身就是通过Preson构造函数创建的 alert(preson1 instanceof Object); //所有的对象均继承Object
所以可以通过instanceof运算符检测一个对象是否为数组,因为数组是构造函数Array的实例。
var colors = ['yellow']; alert(colors instanceof Array); alert(colors instanceof Object); // 这里我有点不明白?书中说通过字面量的方式创建的数组没有调用构造函数,那么这里的结果不应该是false吗。
通过instanceof运算符检测还有一个问题,它假定只有一个全局执行环境。如果网页中有多个框架,那么就有两个不同的全局执行环境,就有了两个不同版本的Array构造函数,如果其中一个框架像另一个框架传递数组,那么这个框架传递的数组就和被传递框架的数组有不同的构造函数,那么可能是数组的情况可能也会返回false,从而发生误会。
为了解决这个问题,ECMAScript5中,新增了Array.isArray()方法,这个方法只为确定这个对象是不是数组,高版本浏览器支持。
var colors = ['yellow']; alert(Array.isArray(colors)); //这个方法就是添加在Array构造函数上的方法
还可以通过调用Object.prototype.toString()方法,Object原型上的toString()方法来判断
var colors = ['yellow']; alert(Object.prototype.toString.call(colors)); //object Array
Array的实例方法,即在Array构造函数原型中定义的方法
转换方法:
valueOf()方法,toString()方法,toLocaleString()方法。
正如Object的valueOf()方法一样,会返回对象本身,数组也不例外,会返回本身。
var colors = ['yellow', 'blue', 'green']; alert(colors.valueOf === colors); //这里返回的ture
toString()方法,实际上会调用数组每一项的toString()方法,然后将其拼贴成一个字符串
var colors = ['yellow', 'blue']; alert(colors.toString()); //'yellow, blue'; alert(typeof colors.toString()); //返回的是string
关于toString()方法要注意的是,alert()需要接受一个字符串参数,如果不是字符串参数则会调用该参数的toString()方法将其转换为字符串
var colors = ['yellow', 'blue']; colors.toString = function() { return 'change'; //如果改变了colors的toString方法,alert()方法就不会弹出原本的内容了 } alert(colors);
toLocaleString()方法和toString()方法在大多数时候的结果都是相同的,运作原理也相同,在数组调用这个方法的时候,对每一项调用toLocaleString()方法。
另一个方法是join(),toString()方法数组转换为以逗号分隔的字符串,而join()方法的作用可以说是相同,只是在不传递参数的情况下或者参数为undefined的情况下,默认将数组以逗号分隔为字符串。如果有其它参数,则按照传入参数进行分隔。
var number = [1,2,3,4,5,6,7,8,8]; //~不用colors了 alert(number.join()); //不传递参数逗号分隔 alert(number.join(undefined)); //传递undefined依然逗号分隔,在ie7以及更早的版本会用字符串‘undefined’来当作分隔符号 alert(number.join('%^&*')); //自定义分隔符号
join()方法是Array.prototype.join()方法,这个方法是一个函数,可以使用函数的call方法在字符串或者类似数组的对象的作用域中使用,同样可以转换为字符串并特定分隔
var str = 'apple'; alert(Array.prototype.join.call(str, '*')); //字符串被分隔,这里同样如果不传参数或者undefined默认以逗号分隔。
注意的是什么样的对象是类似数组的对象呢?数组虽然看不到索引,但是有默认的索引0,1,2,3这样,还有一个length属性,可以用对象模拟这种方式
var obj = {0: 'zhagnxueyou', 1: 'liudehua', 2: 'jinchengwu', length: 3}; //这样就跟数组一样了,就可以调用Array.prototype.join()方法 alert(Array.prototype.join.call(obj, '*')); //这里同样如果不传递参数会默认逗号分隔
另一个需要注意的地方是,对于valueOf(),toString(),toLocaleString(),join()这三个方法,如果数组中出现了null或者undefined,则返回值用空字符串表示。
var arr = [null, undefined, 'null', 'undefined']; alert(arr.valueOf()); alert(arr.toString()); alert(arr.toLocaleString()); alert(arr.join('~~')); //可以得到这个结论,特殊值undefined和null都被以空字符串来表示
栈方法:后进先出的数据结构,即最后添加的最先被移除,由两个方法构成
push()方法和pop()方法。
push()方法用于像数组的末尾添加新的项,可以添加任意参数。同时返回更新后的数组长度,即length。
var colors = ['yellow', 'blue']; var result = colors.push('white', 'red'); alert(colors); //可以看到数组被更新了 alert(result); //可以看到返回了更新好的length值
push()方法的参数可以是任意类型,甚至还可以是另一个数组。
var colors = ['yellow', 'blue']; var obj = {name: 'liuhaixiao', age: 12}; var number = [1,2,3,4,5]; colors.push(obj, number, true, {school: 'beijing'}); alert(colors); //可以看到无论是对象还是数组都被添加到原有数组的后面,这里要注意的数组,并不是整个数组追加,而是将数组的每一项追加
使用push()方法像对象添加元素,添加后的对象变成类数组的对象,并且有一个length属性,该属性的值等于添加项的个数。
var obj = new Object(); obj.name = 'liuhaixiao'; var result = [].push.call(obj, 'apple', ['yellow', 'blue'], {age: 18}); alert(result); //这里返回的是添加项的个数,即3 console.log(obj); //索引0对应添加的第一项,length属性在最后,中间是原本对象拥有的属性 console.log(Object.keys(obj)); console.log(Object.getOwnPropertyNames(obj));
pop()方法用于删除数组的最后一项,更新数组的长度,但是返回值是删除的项。
var colors = ['yellow', 'blue', 'green']; var result = colors.pop(); alert(colors); //数组获得了变化,删除了一项,必然更新了length alert(result); //可以看到这里返回的是删除的最后一项即‘green’
对空数组使用pop方法会返回undefined
var colors = []; alert(colors.pop()); //返回特殊值undefined
队列方法:访问规则是先进先出,即从列表的末端添加,在列表的前端移除
push()方法可以实现在后端添加,对应前端移除的方法就是shift(),这个方法删除最前的一项并返回,更新数组的length
var colors = ['yellow', 'green', 'blue']; var result = colors.shift(); alert(colors); //可以看出移除后更新了数组 alert(result); //返回的是被删除的项
通过shift()方法可以清空数组
var colors = ['yellow', 'blue', 'green']; var item; while(item = colors.shift()) { console.log(item); } alert(colors); //这是一个空数组,当然也可以用pop()方法来实现
以为遥相呼应,ECMAScript还提供了一个unshift()的方法,这个方法像数组的前端添加任意数据,用法和push()一样。通过unshift()方法和pop()方法可以实现相反的队列
var colors = ['yellow', 'blue', 'green']; var result = colors.unshift('red', 'white'); alert(colors); //更新后的数组 alert(result); //更新的length值
重新排序方法:
reverse()方法和sort()方法
reverse()方法用于倒转数组排序
var number = [1,2,3,4,5,6,7]; number.reverse(); alert(number); //可以看到数组被倒转了
sort()方法用于排序,在默认情况下会按照升序的情况进行排序并且会调用数组每一项的toString()方法先将其转换为字符串,所以说排序比较的是字符串,并且按照字典序,我的理解就是abcdef这样排序
var number = [12, 1, 41, 2, 4, 0]; alert(number.sort()); //这里可以看到并没有如愿以偿按照从小到大的顺序排序
正因为是这样sort()方法可以接受一个比较函数,该函数有两个参数,如果第一个参数位于第二个返回之前一个负数,第一个参数位于第二个之后返回正数,相等则返回0
var number = [12, 1, 41, 2, 4, 0]; number.sort(function(value1, value2) { if(value1 < value2) { return -1; } else if(value1 > value2) { return 1; } else { return 0; } }); alert(number); //这回按照从小到大的顺序排序了 //如果想改变排序的行为,即按照降序排序,则可以将返回值互换更改
比较函数可以简写,如果对于数值类型或者valueOf()方法能够返回数值类型的对象,可以简化比较函数
var number = [12, 1, 41, 2, 4, 0]; function compare = function(value1, value2) { return value1 - value2; } number.sort(compare); alert(number); //更上面的方法一样只不过更简洁
对象数组的比较可以这样来写
var friend = [{name: 'zhangsan', age : 23}, {name: 'lisi', age: 12}, {name: 'wangerma', age : 8}, {name: 'wagnwu', age: 18}]; //这是一个对象数组 friend .sort(function(value1, value2) { return value1.age - value2.age; //这里的参数value1,value2分别对应数组的每一项 }); console.log(friend); //查看重新排序的数组是,按照每一项的age属性来排序的
将汉字通过首字母abcd排序,可以这样写比较函数
var names = ['毕福剑', '阿信', '张信哲', '陈楚生']; names.sort(function(str1, str2) { return str1.localeCompare(str2); }); alert(names);
对数组中的每一项随机排序,可以这样写比较函数
var names = ['毕福剑', '阿信', '张信哲', '陈楚生']; function compare(n1, n2) { return Math.random() - 0.5; } setInterval(function() { names.sort(compare); console.log(names); //可以看到每次都输出从新随机排序的数组 }, 2000);
以上这些方法包括push(),pop(),shift(),unshift(),reverse(),sort(),这些方法有一个共同的特征就是都会改变原有的数组。但是join()方法就跟toString()方法一样不会。
操作方法:
操作方法包含三个方法分别是:concat(),slice(),splice()
concat()方法用于合并数组或者将新项添加到数组的后面,如果没有参数将创建一个副本,这个副本是一个浅拷贝的复制,也就是说如果数组的项是引用类型(对象),则会复制这个对象的引用而不是具体的值。
var colors = ['yellow', 'blue', 'green', 'red']; var result = colors.concat('white', 'black', [12,13], {name: 'liuhaixiao'}); //合并多个,甚至对象也可以 console.log(result);
所谓复制这个对象的引用应该是这样的
var colors = [{name: 'yellow', age: 'blue'}]; var result = colors.concat(); result[0].name = 'liuhaixiao'; //concat()方法不会修改原始的数组,所以按照道理来说这个更改并不会影响原始数组 console.log(colors); //但是查看后却更改了,因为复制的只是引用类型的引用地址,虽然复制出了一份,但是访问的还是同一个引用地址
借助call()方法,concat()方法可以将对象合并成数组
var result = [].concat.call({name: 'liuhaixiao'}, {age: 18}); console.log(result); //这样的数组是包含的两个对象
slice()方法,slice本身的意思是切片、不部分。这个方法基于原有的数组创建一个新数组,新数组的项是原数组的子项。接收一个或者两个参数,如果只接收了一个参数,表示从开始的索引到数组结束,如果接受了两个参数表示从索引开始到索引结束(并不包含结束位置)
var colors = ['yellow', 'blue', 'green', 'white', 'red']; var result = colors.slice(2); //如果从索引2开始,那么就包括green、white、red alert(result); var result2 = colors.slice(2,4); //如果从2开始到4,则green、white,因为并不包含结束位置 alert(result2);
slice()方法可以接收负数参数,如果是负数参数则用参数加数组的length来确定位置(即索引的倒数位置)。意外,结束位置小于开始位置,则返回空数组。为了永远不是空数组,在接收负数参数的时候,要确保第一个参数的绝对值大于第二个参数。
var colors = ['yellow', 'blue', 'green', 'white', 'red', 'grey']; var result = colors.slice(-5, -2); alert(result); //用length确定正确位置则是(1, 4),blue,green,white var result2 = colors.slice(-2, -5); alert(result2); //用length确定位置则是(4, 1),结束位置小于开始,返回空数组
slice()方法如果两个参数相等,无论是正数还是负数都会返回空数组,这里好像也没必要出现这种情况haha~~。如果没有参数则相当于拷贝,等同于concat()方法不传递参数。
var colors = ['yellow', 'blue', 'green', 'white', 'red', 'grey']; var result = colors.slice(2, 2); var result2 = colors.slice(-2, -2);
slice()方法的重要应用,将类数组对象转换为真正的数组
var obj = {'0': 'zhangxueyou', '1': 'liudehua', length: 2}; var result = Array.prototype.slice.call(obj); //将对象转换成数组 alert(result); //查看数组 alert(Array.isArray(result)); //看这个对象到底是不是数组
javascript中有许多对象都是类数组,比如arguments,或者选出的元素合集,他们有length属性能用方括号访问,看起来很像数组但是他们并不是,用slice()方法同样可以将他们转换为真正的数组。记得在高级程序设计书中有这个,但是忘记在那里了,没找到,往后看应该就看见了。
var oDiv = document.getElementsByTagName('div'); //选出一组div合集 alert(Object.prototype.toString.call(oDiv)); //确定他们具体的对象类型HTMLCollection var result = Array.prototype.slice.call(oDiv); //将这个div合集转换为真正的数组 alert(Object.prototype.toString.call(result)); //判断,是真的数组很nice
splice()方法是数组的万能方法,根据传递参数的不同,可以实现数组的删除,添加和替换。这个方法在改变原有数组的同时,返回的是删除的项,如果没有删除则返回空数组。
删除:splice()方法接收两个参数,从那里删除,和删除的项数(这里并不是索引而是删除的个数要注意)
var colors = ['yellow', 'blue', 'green', 'white', 'red', 'grey']; var result = colors.splice(2, 2); //从索引2开始删除,删除两项 alert(colors); //green和white被删除 alert(result); //返回的是被删除的项
添加:splice()方法接收三个参数,从那里添加,删除为0,添加的项(可以任意多个值)
var colors = ['yellow', 'blue', 'green', 'white', 'red', 'grey']; var result = colors.splice(0, 0 '我不是颜色'); //这里的添加是在索引前面 alert(result); alert(colors);
替换:splice()方法接收三个参数,从那里开始替换,删除个数,替换的值(任意多个)
var colors = ['yellow', 'blue', 'green', 'white', 'red', 'grey']; var result = colors.splice(2, 1, '我不是颜色'); //如果替换的值为多个,那么就是替换和追加 alert(colors); //删除green,添加我不是颜色
这三个操作数组的方法其中concat()和slice()都是基于原有数组创建新的数组,原有数组不会改变,splice()方法是对数组进行操作会改变原有的数组。
ECMAScript中新增了两个位置查找方法indexOf()和lastIndexOf(),这两个方法接收两个参数,要查找的项,和查找的起始位置(可以省略)。如果找到了返回位置索引,如果没找到则返回-1。这两个方法的却别在于,前者是从开始像后查找(找第一次出现的位置),后者是从后向前查找(也就是最后一次出现的位置)。
var colors = ['yellow', 'blue', 'green', 'white', 'red', 'grey']; alert(colors.indexOf('blue')); //返回的是查找项的索引 alert(colors.indexOf('blue', 1)); //从索引1开始也正好找到,索引并不变 alert(colors.indexOf('blue', 2)); //从索引2开始就找不到了
这两个方法在判断查找的项和数组中某项是否相等的时候使用的是===(全等操作符)。这意味着对象数组的查找要看值和引用地址是否相等。而特殊的NaN不和任何值相等包括自己,会永远返回-1。
var colors = ['yellow', NaN , 'green', 'white', 'red', 'grey']; alert(colors.indexOf(NaN)); //找不到因为NaN和任何值都不想等
ECMAScript5新增了5个迭代方法(循环数组中的每一项),总体来说这五个方法都接受两个参数,第一个是函数(数组的每一项依次执行该函数),第二个是可选的运行函数作用域的对象-----this值。这个函数接收三个三处,数组的项、数组项的索引、和数组本身。
some()方法和every()方法,这两个方法用于判断数组的项是否符合某种条件,前者只要有一项符合条件那么就返回true,后者必须全部符合条件才会返回true。
var colors = ['yellow', NaN , 'green', 'white', 'red', 'grey']; var result = colors.some(function(item, index, array) { return typeof item == 'string'; //每一项是否都是字符串 }); alert(result); //true,some方法只要有一个项符合就会返回true var result2 = colors.every(function(item, index, array) { return typeof item == 'string'; }); alert(result2); //有一项不符合就返回了false
对于空数组来说some()方法会返回false,every()方法会返回true。两者都不会执行其中的函数,因为没有项。但是啊,没有必要为空数组执行这些方法啊。
var colors = []; var result = colors.some(function(item, index, array) { return item > 2; }); alert(result); //false var results = colors.every(function(item, index, array) { return item > 2; }); alert(results); //true
filter()方法和map()方法,这两个方法都返回一个数组。前者为过滤,即返回符合函数执行结果的数组。后者返回对每一项执行函数结果的数组。
var colors = ['yellow', NaN , 'green', 'white', 'red', 'grey']; var result = colors.filter(function(item, index, array) { return typeof item === 'string'; }); alert(result); //返回数据类型是字符串的数组
利用第二个参数来设置this
var number = [1,2,3,4,5,6,6,7,8]; function Obj() { this.Max = 3; } var result = number.filter(function(item, index, array) { return item > this.Max; }, new Obj); alert(result);
map()方法返回计算后的结果数组
var number = [1,2,3,4,5,6,7]; var result = number.map(function(item, index, array) { return item * index; //返回的是对每一项*索引的结果 }); alert(result);
map()方法不仅对数组使用,也可以对字符串使用,循环操作字符串的每一个子串,并将字符串转换为一个数组。
var colors = ['yellow', 'blue', 'green']; var str = 'apple'; function toUpper(item) { //讲这个函数传递给map()方法,那么这里的参数就是数组的每一项了 return item.toUpperCase(); } var resultArray = colors.map(toUpper); alert(resultArray);
上面的对数组的使用,下面看字符串
var str = 'apple'; function toUpper(item) { return item.toUpperCase(); } var result = [].map.call(str, toUpper); alert(result); alert(Array.isArray(result));
也可以用操作方法将字符串转换为数组,在执行相应的操作,如果最终想得到字符串还可以继续转换
var apple = 'apple'; var result = apple.split('').map(function(item) { return item.toUpperCase(); }); alert(result); var resultToString = result.join(); alert(typeof resultToString);
map()方法的第二个参数改运行的作用域this的指向
var colors = ['yellow', 'blue', 'green', 'red', 'gery']; var number = [2,3,1]; var result = number.map(function(item) { return this[item]; //this为colors,而item是应用数组的项 }, colors); alert(result);
如果应用map()方法的数组有空位,即用逗号分隔,中间没有显示的赋值undefined,null,或者其它具体的数据类型,则传递函数不会对该项执行任何操作
var colors = [undefined, null, 'blue', 'green']; var fruit = ['apple', , , , 'banana']; function addString(item) { return item + '我被增加了'; } var colorsAddString = colors.map(addString); var fruitAddString = fruit.map(addString); alert(colorsAddString);
alert(fruitAddString); //可以清楚的看到空的位置,没有进行任何操作
最后一个方法是forEach()方法,这个方法和map()方法类似,只是不返回值。这个方法同样会跳过空位置。
输出数组的每一项
var colors = ['yellow', 'blue', 'green']; colors.forEach(function(item, index, array) { console.log('[' + index + '] = ' +item + ' Array: ' +array); });
forEach()方法不会有停止条件,会将每一项都循环,如果需要停止条件判断应该使用for循环
var colors = ['yellow', 'blue', 'green', 'gery', 'red']; colors.forEach(function(itme, index, array) { if(item == 'gery') break; //会出现错误,无法中断 console.log(item); });
var colors = ['yellow', 'blue', 'green', 'gery', 'red']; for(var i=0; i<colors.length; i++) { if(colors[i] == 'green') break; //使用for循环就没有问题 console.log(colors[i]); }
归并方法:ECMSScript5中定义了两个归并数组的方法,这两个方法都会迭代数组中的所有项,构建一个最终值并返回。
这两个方法是reduce()和reduceRight(),这两个方法接受两个参数,一个函数,和一个作为归并基础的初始值。这个函数接受四个参数,分别是前一个值(循环一次后为累计变量),当前值,项的索引(默认为0)和数组本身。
var sum = number.reduce(function(prev, cur, index, array) { return prev + cur; //第一次执行函数的之后,prev为1,cur为2,第二次执行函数的时候,prev的值是第一次执行函数的结果即3,cur的值为第三项3。 }); alert(sum)
如果通过第二个参数指定了初始值,那么prev就是指定的值,而cur就不再是数组的第二项了而是第一项。
var number = [1,2,3,4,5]; var sum = number.reduce(function(prev, cur, index, array) { return prev + cur; }, 10); alert(sum);
空数组reduce()方法的时候,会取不到初始值然后报错,可以通过第二个参数指定初始值
var number = []; var sum = number.reduce(function(prev, cur, index, array) { return prev + cur; }, 10); alert(sum); //必然是10
reduce()方法的应用,找出数组中最长的项,只适合与字符串
var colors = ['yellow', 'blue', 'green']; colors.reduce(function(prev, cur, index, array) { return cur.length > prev.lenth ? cur : prev; //如果cur的length大于初始,则返回cur,否则返回prev,然后继续迭代 }, '');
通过在原型中利用reduce()为数组添加求和方法
Array.prototype.sum = function() { return this.reduce(function(prev, cur) { return prev + cur; }); } var number = [1,2,3,4,5,6,7,8,9,10]; alert(number.sum());
至于reduceRight()方法和resuce()方法相同,只不过从最后一项开始向前迭代。但是在特殊情况下也会有些不同,但这这样的不同没必要担忧吧。
var number = [1,2,3,4,5]; var result1 = number.reduce(function(prev, cur) { return prev - cur; }); alert(result1); var result2 = number.reduceRight(function(prev, cur) { return prev - cur; }) alert(result2)
参考资料:javascript高级程序设计(第三版)
阮一峰老师的博客教程:http://javascript.ruanyifeng.com/

浙公网安备 33010602011771号