本篇文章源于对 《JavaScript 高级程序设计》 第五章 Array 类型的精简归纳再加以本身的扩展总结而来。前端
组成数组的每一项能够称之为 “数组元素”,也能够称之为 “数据项”,而且每一个数组元素都有一个与之对应的下标(索引)。 ECMAScript 中建立数组主要有两种方式:算法
构造函数数组
var arr = new Array(); //建立一个默认的空数组
var arr2 = new Array(3); //建立一个空数组,但初始化好数组的长度
var arr3 = new Array(1, 2, 'a'); //建立一个数组,并依次按顺序保存传入的数组元素的值。
复制代码
在这个例子中
arr
、arr2
、arr3
都是构造函数Array()
的实例数组对象。浏览器
字面量数据结构
var arr = [1, 2, 3, 'a', 'b', 'c'];
复制代码
须要注意的是,若是数组中存在以逗号隔开的空值,那么数组元素的数量,在不一样的浏览器将存在兼容性问题。框架
var arr = [1, 2, ];
/* IE 8.0 - */
arr.length // 3;
/* IE 9.0 + / Chrome / Firefox */
arr.length //2
复制代码
在
IE8.0-
下只要有逗号分隔就存在着数组元素,默认为undefined
。函数
Array 的长度是动态的,经过 length
属性能够实时获取数组的当前长度,但 length
属性却并不是是只读的,经过 length
属性咱们能够手动调整数组的长度,删除数组尾部的元素,也或者向数组的尾部新增数组元素。oop
var arr = [1, 'a', true];
arr.length; //3
复制代码
arr.length = 2;
arr[2]; // undefined
复制代码
arr.length = 3;//(3) [1, "a", empty]
arr[arr.length] = false; //(4) [1, "a", empty, false]
复制代码
isArray()
是 ES5 为 Array 类型最新添加的静态方法,它能够有效的解决不是一个全局环境中的“引用类型”是否为数组的判断。 例如:从 A 框架(iframe)中传入到 B 框架的数组就不是一个全局环境,对于这种状况,单纯的 instanceof
操做符将会失效。性能
Array.isArray(arr);
复制代码
返回值是一个 Boolean
类型,兼容性:IE9+。ui
若是考虑兼容性而且笃定当前的引用类型不会跨全局环境,则能够直接使用 instanceof
操做符。
arr instanceof Array; //true
复制代码
也或者经过引用类型的 constructor
属性来判断:
arr.constructor == Array // true
复制代码
更或者能够基于行为来判断:
function isArray(target) {
if (typeof target != 'object') return;
return target.length >= 0 && target.pop && target.push;
}
复制代码
最后,还能够经过 Object.toString()
方法返回的固定格式结果来进行类型的匹配。
function isType(target, type) {
return Object.prototype.toString.call(target) === '[object ' + type + ']';
}
isType([],'Array');
复制代码
多种方法相比较,第一种最适用,但存在兼容性问题,而第二种没法解决跨全局的问题,最后的两种无疑更加通用。
在 JavaScript 中一切皆对象,其中 Object
是一切其它对象的基础对象,全部其它对象都继承于 Object
,因此同理,Array 也具备 toString()
、toLocaleString()
、valueOf()
这三个方法,只是 Array 中这三个方法并非单纯的继承于 Object,而是对这三个方法进行了必定特殊的重写,差别则体如今 Array 中这三个方法返回值并不与 Object 的相同,而是更契合自身的结果。
var a1 = [];
var a2 = ["red", "green", "blue"];
var a3 = [{}, {}];
复制代码
toString()
toString()
与 toLocaleString()
方法会将数组元素转换为字符串,并以“逗号”做为默认的分隔符进行拼接返回。
a1.toString(); //"";
a2.toString(); //"red,green,blue"
复制代码
当数组元素是基本数据类型时,这一转换会很直观,可是当数组元素的值是一个“引用类型”时,结果将会有些不一样。
a3.toString(); //"[object Object],[object Object]"
复制代码
这是由于 Array 的 toString()
方法遇到“引用类型”的值时会继续调用这个“引用类型”的 toString()
方法或 toLocaleString()
,直到返回的结果是一个基本类型为止。
var f = function() {};
var r = new RegExp();
var b = new Boolean(false);
[f, r, b].toString(); // "function(){},/(?:)/,false"
复制代码
更严谨的说当 Array 的 toString()
方法遇到的是一个“引用类型”的值时会先调用这个引用对象的 valueOf()
方法,当 valueOf()
方法的返回值依然是一个引用类型时,才会调用这个对象的 toString()
方法,直到返回的值是一个基本数据类型为止。
var arr = [{
valueOf: () => { return 1 },
toString: () => { return 'A' }
}, {
valueOf: () => { return { toString: () => { return 2 } } },
toString: () => { return 'B' }
}];
arr.toString(); // "A,B"
复制代码
valueOf() 返回数组实例对象自己。
join()
数组除了以上全部引用类型都具备的三个基本转换方法,还有一个私有的转换方法 — join()
。
join()
与 toString()
功能很是类似,区别只是 toString()
只能默认以“逗号”做为分隔符来拼接每一个转换为字符串的数组元素,而 join()
方法则能够自定义要拼接的分隔符。固然若是不指定特定的分隔符,join()
方法默认的也是以“逗号”做为分隔符。
var arr = ['red', 'green', 'blue'];
arr.join(); // "red,green,blue"
arr.join('|'); // "red|green|blue"
arr.join('||'); // "red||green||blue"
复制代码
利用 Array 自带的一些方法,咱们能够很轻易的模拟其它的数据结构,例如:“栈”(堆栈)。 “栈”是一种后进先出(先进后出),LIFO 的数据结构,也就是最新添加的最先被删除,不过在栈这种数据结构中,向栈中添加数据被称之为“入栈”(推入),移除数据则称之为“出栈”(弹出),所以在栈中,数据的入栈与出栈只会在一个位置进行 — 既“栈”的顶部(栈头)。
对“栈”的更形象理解,能够想像成一个空的容器,容器的底部是栈底,而容器的入口即是栈头,而后不断的向这个容器内部加入物件,一直塞满为止,当容器已经塞满的时候,若想再加入只能在容器的入口处将最近一次新添加的物件移出。
ECMAScript 实现栈的行为主要借助 push()
、pop()
等方法。
push()
:向数组的尾部追加新的数组元素。返回更新后的数组长度。pop()
:删除数组尾部的最后一项,返回被删除的数组元素。经过使用 pop()
、push()
来模拟“栈”的结构。那么栈头的位置就是数组的尾部,由于这两个方法都是对数组的尾部进行操做。
//假设“栈”最多只能放置3个物件。
var number = new Array(3);
number.push(1);
number.push(2);
number.push(3);
//此时从新加入第4个物件,则必须将栈顶的物件3移除。
number.pop();
//移出后,即可以再加入物件4.
number.push(4);
复制代码
“栈”的数据结构使其操做与访问要遵循“后进先出(LIFO)”的规则,而“队列”这种数据结构使其访问与操做要遵循 “先进先出(FIFO)”的规则,队列中的数据,从队列的末端被添加,而后在队列的前端移除。 形象的理解,队列(Queue)就像是现实生活中排队买东西同样,先排先处理,后来者则在队尾进行排队等待处理。
使用数组来模拟队列,须要借助 shift()
、push()
等方法。
shift()
:删除数组第一个元素,并返回被删除的数组元素。//已经排好的队伍
var queue = [1, 2, 3];
//此时来了第四我的,则向后排队
queue.push(4);
//开始处理每次队列的第一我的
queue.shift();
queue.push(5);
queue.shift();
//....
复制代码
通常来讲这种从队首移除,从队尾添加的顺序是默认的队列规则,固然也能够从队首添加,队尾删除,像这种队列则称之为 “反向队列 (reverse queue)”。
实现反向队列则须要借助 unshift()
与 pop()
。
unshift()
:向数组的首部添加元素,并返回添加后的数组长度。var colors = new Array();
colors.unshift("red", "green");
colors.unshift("black");
colors.pop(); //green
复制代码
数组中最简单的排序方法即是 reverse()
。 reverse()
方法会对原数组进行颠倒重排,并永远改变原数组中数组元素的顺序。
var arr = [1, 2, 3];
arr.reverse();
arr; //[3,2,1]
复制代码
再复杂些的排序方法即是数组的 sort()
方法,sort()
方法默认升序排序,而且会自动将数组元素转换为字符串,而后按照 ASCII 码大小进行排序。例如:
var arr = [0, 1, 5, 15, 10];
arr.sort();
arr; //[0,1,10,15,5];
复制代码
若是只是单单的将数组元素转换为字符进行大小比较,必然没法知足一些更为复杂的使用场景,所以 sort()
方法也接收一个回调函数,来自定义排序的行为:
function compare(value1, value2) {
if (value1 < value2) return -1;
if (value1 > value2) return 1;
return 0;
}
arr.sort(compare); //[0,1,5,10,15];
复制代码
sort()
的回调函数能够接收两个参数,若是第一个参数应在第二个参数以前,则回调函数返回一个负数,若是第一个参数在第二个参数以后,则返回一个正数,若是两个参数相等则返回 0 .
在上面的示例中,由于采用的是升序排序,因此会对值的大小进行判断(若是第一个值小于第二个值,既第一个值要在第二个值以前,此时返回 -1,若是第一个值大于第二个值,既第一个值实际要在第二个值以后,此时返回 1,除了以上两种状况,默认则返回0)。
一样的,若是打算降序排序,则只需将以上条件的返回值取反便可。
if (value1 < value2) return 1;
if (value1 > value2) return -1;
复制代码
一般状况下,若是咱们能保证要进行排序的数组,其数组的元素都是数值类型,并默认升序排序,直接返回两个参数的的差值便可。
function compare() {
return value1 - value2;
}
复制代码
经过传入自定义排序方法,还能够解决结构较为复杂的数据排序问题。
[
{ id: 3, name: 3 },
{ id: 5, name: 5 },
{ id: 4, name: 4 }
]
复制代码
若要对这样的 JSON 数据按照 id 排序,则只需在 sort()
方法中进行 id 大小的比较便可。
arr.sort(function(a, b) { return a.id - b.id; });
复制代码
实际上 sort
方法的实现标准并无归入 ECMAScript 中,所以不一样的浏览器,采用的算法也不相同,就以 Chrome 为例,在排序数组长度较短的时候,采用“插入排序”,当数组较为复杂的时候则采用“快速排序”算法。
这里简单的介绍下“插入排序”,咱们能够想象如今有一个队伍(数组),队伍中人的年龄分布是无序的,如今要按照人的年龄大小来对对队伍进行排序(升序),若采用“插入排序”算法,即是从队伍的第二我的开始,让它与以前的人对比,若是前面的人年龄比本身大,则调换位置,调换位置后继续向当前位置的前一个比较,按照这个顺序继续比较下去,直到发现前一我的的年龄比本身小为止。 当第二个比较完毕后,接着即是队伍中的第三个数也依次进行大小比较和位置的相互交替,直到队伍的最后一我的对比完成方才结束整个轮番对比。
简单的说,插入排序的方式有些傻,首先不考虑队伍中的第一我的,直接从第二我的开始抓壮丁,先拉着第二我的与被忽略的第一我的比较,若是第二个大于第一个,则保持位置不变,接着再拉第三个壮丁,再依次于第二我的,第一我的比,若是第三我的的年龄小于第二我的,则二者交替位置(本来的第三变成了第二,本来的第二如今是第三)再接着从当前的位置继续向第一个比较.... 按照这样的规则每次用一我的跟队伍全部人轮番比较,直到最后一我的为止,因此“插入排序”在数据量小的状况下更简单明了,可是在数据量大的状况下,性能并不是最理想。
下面是用 ECAMScript 来实现一个简单的 “插入排序”:
function insertSort(arry) {
var temp;
var j;
//从第二个开始
for (var i = 1; i < arry.length; i++) {
//保存被对比的数 (每次的壮丁)
temp = arry[i];
//初始化第一个要对比数的下标。
j = i - 1;
while (arry[j] > temp) {
//若是被对比数小于当前进行对比的数,则互换位置。
arry[j + 1] = arry[j];
//继续轮询对比
j--;
}
/* * 循环结束,则最后一个位置的索引 +1 * 知足条件: j--,不知足条件:j=i-1,便老是当前对比数的位置 * 也就是不论有没有知足条件,总要把壮丁再放回去。 */
arry[j + 1] = temp;
}
return arry;
}
复制代码
下面是 “插入排序的GIF” 演示:
注意的是
sort()
方法也会永远改变原数组中元素的排列顺序。
下面是 ECMAScript 提供的一些对数组进行操做处理的方法。
var rgb = [000, 255];
var hex = ['#000', '#fff'];
var color = rgb.concat(hex);
console.log(rgb); //(2) [0, 255]
console.log(hex); //(2) ["#000", "#fff"]
console.log(color); //(4) [0, 255, "#000", "#fff"]
复制代码
concat()
方法还能够同时合并多个数组。
rgb.concat(hex, hsl, ymlk);
复制代码
concat()
只是合并数组,返回一个全部数组的合集,并不会像 sort
,reverse
等方法同样会永远改变数组的结构与数据项的顺序。
slice
方法能够根据参数的起始位置与结束位置,从原数组中截取一段数组元素组成一个新的数组,并不会改变原数组对象。
Array.slice(start,end)
复制代码
start
是截取的起始位置,end
则是截取的结束位置,但并不会包含 end
所指向的那个数据项,也就是每次截取的都是 (start ~ end - 1)
。用区间表示:[a,b)
。
var colorArr = ['red', 'green', 'blue'];
colorArr.slice(1, 1); //[]
colorArr.slice(1, 2); //["green"]
复制代码
若是 end
结束位置为空,则表示从开始位置值截取到数组的末尾。 若是 end
结束位置为负数,则用这个负数加上数组的长度,获得的结果才是 end 真正的值。
colorArr.slice(0, -2); //["red"]
复制代码
使用数组的 splice
方法能够对原数组进行插入/删除/覆盖操做。 其返回值是数组中被删除的或被覆盖的元素组成的新数组。若是进行的是插入操做那么返回的值即是一个空数组。 注意:splice 方法会改变原数组。 格式:Array.splice(S[, R, item1, item2, item3, ...])
S
指的是在原数组中进行操做的起始位置。 R
表示要删除或要覆盖的元素个数。 item
是要插入或覆盖(R值存在)的数组元素,数量不限。当 item
存在而且 R
为 0 ,则进行的是插入操做,当 R
不为 0,也存在 item 时,则进行的是覆盖操做。
//删除操做
var colors = ["red", "green", "blue"];
var A = colors.splice(0, 1);
console.log(colors); //(2) ["green", "blue"]
console.log(A); //["red"]
//插入操做
var B = colors.splice(0, 0, "yellow");
console.log(colors) //(3) ["yellow", "green", "blue"
console.log(B); //[]
//覆盖操做
var C = colors.splice(1, 1, "purple");
console.log(colors) //(3) ["yellow", "purple", "blue"]
console.log(C); //["green"]
复制代码
ECMAScript5 为数组对象新增了两个位置方法:indexOf()
与 lastIndexOf()
。 它们与 String
对象的方法在参数与功能上很是相同。都接收两个参数,一个是要查找的数组元素,另外一个则是被查找数组中的起始位置(默认为0)。 它们的区别,顾名思义,一个从数组的开头向后查找(indexOf),另外一个则从数组的末尾向前查找(lastIndexOf),它们的返回值都是一个数值型,即该元素在被查找数组中的索引位置。 若是没有匹配到,则返回 -1。
[1, 2, 3].indexOf(3,1); //2
[1, 2, 3].indexOf(2,2); //-1
复制代码
indexOf
与 lastIndexOf
也能够查找一个引用类型的数组元素,但被查找数组中若没有相同引用的数组元素,那么必然返回 -1 。
var person = { name: 'zhangsan' };
var poople = [{ name: 'zhangsan' }];
var wrapPerson = [person];
poople.indexOf(person); // -1;
wrapPerson.indexOf(person) //0
复制代码
另外 indexOf()
与 lastIndexOf()
的结合使用还能够巧妙的对数组进行去重。例如去除数组 A 与数组 B 中重复的元素,并用一个新的数组保存二者不重复的元素。
var A = [1, 2, 3, 4, 3, 2, 1];
var B = [1, 2, 3, 5, 3, 2, 1];
var C = A.concat(B);
var repet = [];
for (var i = 0; i < C.length; i++) {
if (C.indexOf(C[i]) === C.lastIndexOf(C[i])) {
repet.push(C[i]);
}
}
复制代码
ECMAScript5 专门为数组新增了5个迭代方法:some、every、filter、map、forEach
。 它们都接收两个参数:
this
。其中迭代器方法又接受三个参数,每次迭代的数据项,每次数据项的索引以及数组对象自己。
var Arr = [1, 2, 3];
var className = { name: "班" };
var classes = Arr.map(function(item, index, arr) {
console.log(index);
console.log(arr);
return item + this.name;
}, className);
复制代码
迭代器函数中的做用域对象能够解决迭代器函数内部对上一级做用域的引用,固然若是学到了 ES6 箭头函数则会更加方便高效。
下面从这五个迭代方法的使用频率来逐个讲解:
some / ervery 关键字:迭代查询 返回值:Boolean。
true
,则 some
迭代的结果便为 true。除非全为 false。var Arry = [1, 2, 3, 4, 5];
var Some = Arry.some(function(item) { return item > 2 });
var Every = Arry.every(function(item) { return item > 2 });
Some // true。
Every // false。
复制代码
简单来讲:some()
方法只要数组中有某一个元素知足特定条件,则返回 true
。every()
方法只有数组中的全部元素都知足特定的条件,才会返回 true
。
filter 关键字:迭代筛选 返回值:数组(Array) 功能:返回由迭代器方法返回值为 true 时的数据项组成的新数组。 示例:
var Filter = Arry.filter(function(item) { return item > 2 });
Filter //[3,4,5]
复制代码
map 关键字:迭代返回 功能:经过迭代器方法每次返回的数组元素组成新的数组。 返回值:Array。
var map = Arry.map(function(item) {
if (item > 2) return item * 2;
return item;
});
nap; // [1, 2, 6, 8, 10]
复制代码
forEach 关键字:纯粹(pure)迭代。 说明:forEach
相似于 for 循环,单纯只为迭代数组元素,并不返回值。
最后,迭代方法一览表:
方法名 | 返回值 | 应用场景 |
---|---|---|
some | Boolean | 遍历检索数组中元素是否知足特定的条件 |
every | Boolean | 遍历检索数组中元素是否知足特定的条件 |
filter | Array | 筛选符合条件的数组项,组成新的数组 |
map | Array | 经过每次遍历返回的值组成新的数组 |
forEach | / | 单纯遍历,无返回值 |
数组的归并方法与迭代方法相同均可以对数组进行遍历。 归并方法也接收一个迭代器函数,做为遍历时的处理函数,这个处理函数同时也接收四个参数。
Arry.reduce(prev, cur, index, array);
复制代码
与迭代方法不一样的是,归并方法的第二个参数不是用于指定回调函数的“做用域对象”,而是初始遍历的基础值(能够理解这个值才是遍历数组的第一个元素)。 数组的归并方法,经常使用于获取数组中全部项之和。
var Arry = [1, 2, 3, 4, 5];
var reduce = Arry.reduce(function(prev, cur, index, array) {
return prev + cur;
}, 1);
reduce //16
复制代码
若是要执行合并的数组元素是引用类型,则须要进行特定的使用:
var arr = [{ num: 1 }, { num: 2 }, { num: 3 }];
arr.reduce((a, b, c) => {
return a + b.num;
}, 0);
复制代码
另外还有一个 reduceRight
方法,其功能与 reduce
相同,参数也相同,区别只是遍历的方向相反。此时做为第二个参数的初始值在 reduceRight
即是最后一个元素。