关于数组的学习,Array类定义的方法用处最大,是学习的重点。在学习这些方法时,要记住其中有些方法会修改调用它们的数组,有些则不会。另外还有些方法会返回数组,有时候返回的是新数组,有时候返回的是被修改的数组的引用。
接下来会集中介绍几个相关的数组方法,并且会探索它们的原理。
- 迭代器方法用于遍历数组元素,通常会对每个元素调用一次指定的函数。
- 栈和队列方法用于在数组的开头或结尾添加或删除元素。
- 子数组(切片)方法用于提取、删除、插入、填充和复制更大数组的连续区域。
- 搜索和排序方法用于在数组中查找元素和对数组元素排序。
数组迭代器
这些方法都会接收一个函数作为第一个参数,并且对数组的每个元素(或某些元素)都调用一次这个函数。如果数组是稀疏的,则不会对不存在的元素调用。多数情况下,该函数调用时会收到3个参数,分别是数组元素的值、索引和数组本身。通常我们只需要这几个参数中的第一个,可以忽略后面两个。
多数迭代器方法还可以接收可选的第二个参数,该参数会成为第一个参数传入的函数内部的this值。
forEach()
forEach()方法 遍历数组的每个元素,并对每个元素调用一次指定的函数。
- 搭建一个HTML结构,一个列表和一组JSON数据。
<ul>
<li></li>
<li></li>
<li></li>
</ul>
<div id="data">
[ { "name": "张三", "age": 18 }, { "name": "李四", "age": 19 }, { "name": "王五", "age": 20 } ]
</div>
- 使用forEach实现列表展示JSON数据。
<script>
var data = JSON.parse(document.getElementById('data').innerHTML),
oLi = document.getElementsByTagName('li');
data.forEach(function(element, index, array){
this[index].innerHTML = element.name;
}, oLi);
</script>
我们可以探究一下forEach()的原理,它循环的过程以及如何修改this指向。
- 注意第二个修改this指向的参数是可选的,所以使用实参列表
arguments[1]去传,如果没有传入第二个参数则依旧指向window。 - call和apply都可以修改this指向,但是
apply更适合传参。
Array.prototype.myForEach = function(fn){
var arr = this,
len = arr.length,
self = arguments[1] || window;
for(var i = 0; i < len; i++){
// call和apply都可以修改this指向,但是apply更适合传参。
fn.apply(self, [arr[i], i, arr]);
}
}
var data = JSON.parse(document.getElementById('data').innerHTML),
oLi = document.getElementsByTagName('li');
data.myForEach(function(element, index, array){
this[index].innerHTML = element.name;
}, oLi);
filter()
filter()方法 返回一个新数组,该数组包含调用它的数组的子数组。传给该方法的函数应该是个断言函数,即返回为true或false的函数,当函数返回true或返回值可以转换为true,则传给这个函数的元素就是最终返回的子数组的成员。
var arr = [1, 2, 3, 4, 5];
var newArr = arr.filter(function(elem){
return elem > 3;
});
console.log(newArr); // [4, 5]
我们继续探究一下filter()的原理。
filter()是要返回一个新数组的,所以其中必定要新建一个空数组,用于存放子数组的成员。- 对于函数返回值的判断,可以用三目运算,如果是
true就push对应的元素(注意push的元素是深拷贝的),如果是false就返回一个空字符串,什么也不做。
Array.prototype.myFilter = function(fn){
var arr = this,
len = arr.length,
self = arguments[1] || window,
newArr = [],
item;
for(var i = 0; i < len; i++){
item = tools.deepClone(arr[i);
fn.apply(self, [item, i, arr]) ? newArr.push(arr[i]) : '';
}
return newArr;
}
var arr = [1, 2, 3, 4, 5];
var newArr = arr.myFilter(function(elem){
return elem > 3;
});
console.log(newArr); // [4, 5]
map()
map()方法 把调用它数组的每个元素分别传给指定的函数,返回这个函数的返回值构成的新数组,不会修改调用它的原数组。
var arr = [1, 2, 3];
var newArr = arr.map(function(element){
return element * 10;
});
console.log(newArr); // [10, 20, 30]
我们继续探究一下map()的原理。
- push的元素就是函数执行的返回值。
- 注意push的元素是深拷贝的。
Array.prototype.myMap = function(fn){
var arr = this,
len = arr.length,
self = arguments[1] || window,
newArr = [],
item;
for(var i = 0; i < len; i++){
item = tools.deepClone(arr[i]);
newArr.push(fn.apply(self, [item, i, arr]));
}
return newArr;
}
var arr = [1, 2, 3];
var newArr = arr.myMap(function(element){
return element * 10;
});
console.log(newArr); // [10, 20, 30]
every()与some()
every()和some()方法都是数组断言方法,即它们会对数组元素调用传入的断言函数,最后返回true和false。
every()方法 它在且在断言函数对数组的所有元素都返回true时才返回true,在断言函数第一次返回false时返回false,也就意味着第一次返回false时停止停止遍历数组。
var arr = [1, 2, 3, 4, 5];
arr.every(function(element){
console.log('test'); // 'test'字符串打印了三次之后停止了遍历。
return element < 3;
});
some()方法 只要数组元素中有一个让断言函数返回true它就会返回true,即第一次返回true时停止遍历数组。只有当断言函数对所有数组元素都返回false时它才会返回false。
var arr = [1, 2, 3, 4, 5];
arr.some(function(element){
console.log('test'); // 'test'字符串打印了一次之后停止了遍历。
return element < 3;
});
重构every()和some()方法,这里只写了every()的原理,some()其实是一样的,取反就行。
Array.prototype.myEvery = function(fn){
var arr = this,
len = arr.length,
self = arguments[1] || window,
bool = true;
for(var i = 0; i < len; i++){
if(!fn.apply(self, [arr[i], i, arr])){
bool = false;
break;
}
}
return bool;
}
var arr = [1, 2, 3, 4, 5];
arr.myEvery(function(element){
console.log('test'); // 'test'字符串打印了三次之后停止了遍历。
return element < 3;
});
reduce()与reduceRight()
reduce()与reduceRight()方法使用指定的函数归并数组元素,最终产生一个值。在函数编程中,归并是一个常见操作,有时也叫注入(inject)或折叠(fold)。
reduce()方法 接收两个参数,第一个参数是执行归并操作的函数。第二个参数是可选的,是传给归并函数的初始值。
- 归并函数中有四个参数,第一个参数用于返回归并操作的累计结果,它的初始值是
reduce()传入的第二个参数。归并函数的其它三个参数就和之前那些函数一样,值、索引和数组本身。
var initValue = [];
var arr = [1, 3, 3, 4, 8, 6, 7, 8, 9, 10];
var newArr = arr.reduce(function(prev, elem, index, arr){
if(elem % 2 === 0){
prev.push(elem);
}
return prev;
}, initValue);
console.log(newArr); // [4, 8, 6, 8, 10]
- 如果没有指定初始值,那么
reduce()调用时会使用数组的第一个元素作为初始值。
var arr = [1, 2, 3, 4, 5];
var res = arr.reduce(function(prev, elem){
console.log(prev);
// 1
// 3
// 6
// 10
return prev + elem;
});
console.log(res); // 15
再来看一个关于reduce()的案例,首先获得div标签中的字符串并通过split()方法分割成数组。再用该数组去调用reduce()方法,在函数体内再次对数组元素进行分割,每一次遍历都将其对应数组元素按=分割成两个子元素。prev的值是第二参数传入的一个空对象,这两个子元素分别作为空对象的属性名和属性值,形成一组键值对。
<div id="J_cookieData">
DSJLFSFLKSD=JDLAJDKSLAJLDKAJKLDALK;DSADA=313213123131;
DKJLASDJKLSADAKLAS=DADKASKLA;DJSALDAK=1231231;
DASJDLAKL=DASD;D1K2=L3KL321JLK;
ASKDSA_DA=12313_2131_ 3131;KDADKAL=3;
SDJKADKLSDLK312LK3=321331JKL2JLK3L;JDAKLD=1231M321M;
</div>
<script>
var cookieDatas = document.getElementById('J_cookieData').innerHTML,
cookieArr = cookieDatas.split(';');
var cookieObj = cookieArr.reduce(function(prev, elem){
var item = elem.split('=');
prev[item[0]] = item[1];
return prev;
}, {});
console.log(cookieObj);
</script>
重构reduce()方法。
Array.prototype.myReduce = function(fn){
var arr = this,
len = this.length,
self = arguments[2] || window,
init = arguments[1] || arr[0];
for(var i = 0; i < len; i++){
init = fn.apply(self, [init, arr[i], i, arr]);
}
return init;
}
var arr = [1, 3, 3, 4, 8, 6, 7, 8, 9, 10];
var newArr = arr.myReduce(function(prev, elem, index, arr){
if(elem % 2 === 0){
prev.push(elem);
}
return prev;
}, []);
console.log(newArr); // [4, 8, 6, 8, 10]
数组拼接
concat()方法 创建并返回一个新数组,新数组包含原数组中的元素以及传给concat()的参数,参数可以是数组形式。该方法常用于数组的拼接。
var arr1 = [1, 2, 3];
var arr2 = arr1.concat(4, 5);
console.log(arr2); // [1, 2, 3, 4, 5]
// 注意原数组并未改变
console.log(arr1); // [1, 2, 3]
栈和队列操作
push()方法 在数组列表末尾添加一个或多个新元素,并返回数组的新长度。
var arr = [1, 2, 3];
console.log(arr.push(4)); // 4
console.log(arr); // [1, 2, 3, 4]
push()方法的原理是根据当前数组的长度决定元素的添加位置的。
var arr = [1, 2, 3];
Array.prototype.pushDemo = function(){
for(var i = 0; i < arguments.length; i++){
this[this.length] = arguments[i];
}
return this.length;
}
console.log(arr.pushDemo(4, 5, 6)); // 6
console.log(arr); // [1, 2, 3, 4, 5, 6]
pop()方法 删除数组最后一个元素,并返回被删除的值。
var arr = [1, 2, 3];
console.log(arr.pop()); // 3
console.log(arr); // [1, 2]
unshift()方法 从数组开头插入元素。
var arr = [1, 2, 3];
console.log(arr.unshift(4)); // 4
console.log(arr); // [4, 1, 2, 3]
- 用
splice()方法重写unshift()方法。
// 重写unshift方法
Array.prototype.unshiftDemo = function(){
var len = arguments.length,
arremp;
for(var i = 0; i < len; i++){
arremp = arguments[i];
this.splice(i, 0, arremp);
}
return this.length;
}
var arr = [1, 2, 3, 4];
arr.unshiftDemo(5, 4, 3, 2, 1);
console.log(arr); // [5, 4, 3, 2, 1, 1, 2, 3, 4]
- 用
concat()方法重写unshift()方法。
// 重写unshift方法
Array.prototype.unshiftDemo2 = function(){
var argArr = Array.prototype.slice.call(arguments);
var newArr = argArr.concat(this);
return newArr;
}
var arr = [1, 2, 3];
var newArr = arr.unshiftDemo2('a', 'b', 'c');
console.log(newArr); // ['a', 'b', 'c', 1, 2, 3]
shift()方法 从数组开头删除元素。
var arr = [1, 2, 3];
console.log(arr.shift()); // 1
console.log(arr); // [2, 3]
数组切片
数组定义了几个处理连续区域(数组“切片”)的方法。
slice()方法 返回一个数组的切片,它可以接收两个参数,分别用于指定要返回切片的起止位置,它不会修改原数组。
slice()传递一个参数时,返回的数组包含从起点开始直到数组末尾的所有元素。
var arr1 = ['a', 'b', 'c', 'd', 'e', 'f'];
var arr2 = arr1.slice(1);
console.log(arr2); // ['b', 'c', 'd', 'e', 'f']
slice()传递两个参数时,返回的数组包含第一个参数指定的元素,以及所有后续元素(但不包含第二个参数指定的元素),简单来说就是[start, end)这样的一个区间。
var arr1 = ['a', 'b', 'c', 'd', 'e', 'f'];
var arr2 = arr1.slice(1, 3);
console.log(arr2); // ['b', 'c']
splice()方法 可以从数组中删除元素或向数组中插入新元素。第一个参数指定插入或删除操作的起点位置。第二个参数指定删除(切割)的元素个数。如果省略第二个参数,从起点位置删除所有元素。该方法返回的是被删除元素的数组。如果没有删除元素则返回空数组。
它和slice()方法不同的是,它是直接对原数组进行修改。
var arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(arr.splice(4)); // [5, 6, 7, 8]
console.log(arr); // [1, 2, 3, 4]
arr.splice(1, 2);
console.log(arr); // [1, 4]
splice()的第一个参数除了是正数,还可以设置为负数,比如最后一个元素的位置也可以用-1表示。
var arr = ['a', 'b', 'c', 'd'];
console.log(arr.splice(-1, 1)); // ['d']
- 负数索引的原理如下:
var arr = ['a', 'b', 'c', 'd'];
function splice(arr, index){
return index += index >= 0 ? 0 : arr.length;
}
console.log(arr[splice(arr, -1)]); // 'd'
splice()的前两个参数用于指定要删除哪些元素,而这两个参数后面还可以跟任意多个参数,表示在指定的位置插入到数组中的元素。并且删除的元素个数可以为0,也就意味着可以直接添加元素。
var arr = ['a', 'b', 'c', 'e'];
// 删除的元素个数为0,所以返回空数组。
console.log(arr.splice(3, 0, 'd')); // []
console.log(arr); // ['a', 'b', 'c', 'd', 'e']
数组索引
indexOf()和lastIndexOf()
indexOf()方法和lastindexOf()方法 从数组中搜索指定的值并返回第一个找到的元素的索引,如果没找到就返回-1。indexOf()从前到后搜索数组,lastindexOf()从后往前搜索数组。
indexOf()方法和lastindexOf()方法还可以接收第二个参数,指定从数组的哪个位置开始搜索。
var arr = [1, 2, 3, 4];
console.log(arr.indexOf(3)); // 2
console.log(arr.indexOf(5)); // -1
数组排序
sort()方法 对数组元素就地排序并返回排序后的数组。在不传参调用时,按字母顺序(ASCII码)对数组元素排序(如有必要,临时把它们转换为字符串比较。)
var arr = [-1, -5, 8, 0, 2];
arr.sort();
console.log(arr); // [-1, -5, 0, 2, 8]
sort()方法传参调用可以对数组元素执行非字母顺序的排序,必须给传一个比较函数作为参数,这个函数中有两个参数。如果返回值小于0,则第一个参数在前面。如果返回值大于0,则第二个参数在前面。
var arr = [27, 49, 5, 7];
arr.sort(function(a, b){
return a - b;
});
console.log(arr); // [5, 7, 27, 49]
// 倒序排列
arr.sort(function(a, b){
return b - a;
});
console.log(arr); // [49, 27, 7, 5]
sort()方法也可以实现随机排序。
var arr = [1, 2, 3, 4, 5, 6];
arr.sort(function(a, b){
// 创建一个0~1之间的随机数。
var rand = Math.random();
return rand - 0.5;
});
console.log(arr);
sort()方法实现按元素的字节数排序。
function getBytes(str){
var sum = 0,
len = str.length;
for(var i = 0; i < len; i++){
var char = str.charCodeAt(i);
if(char > 255){
sum += 2;
}else{
sum++;
}
}
return sum;
}
var arr = ['apple', 'orange', 'hello' ];
arr.sort(function(a, b){
return getBytes(a) - getBytes(b);
});
console.log(arr); // ['apple', 'hello', 'orange']
reverse()方法 对元素就地倒序排列,并返回倒序后的数组。
var arr = ['a', 'b', 'c'];
console.log(arr.reverse()); // ['c', 'b', 'a']
console.log(arr); // ['c', 'b', 'a']
数组与字符串转换
toString()方法 数组中的toString()方法是重写过的方法,把数组中的所有元素转换为字符串,然后把它们拼接起来并返回结果字符串,用逗号分隔。
var arr = [1, 2, 3];
console.log(arr.toString()); // '1', '2', '3'
join()方法 把数组中的所有元素转换为字符串,把它们拼接起来并返回结果字符串。它和toString()方法的区别是可以传递一个可选的字符串参数,用于分隔结果字符串中的元素。如果不指定分隔符,则默认使用逗号。
var arr = ['a', 'b', 'c', 'd'];
var str1 = arr.join();
console.log(str1); // a,b,c,d
var str2 = arr.join('-');
console.log(str2); // a-b-c-d
split()方法 将字符串按指定分隔符转换为数组,它有两个参数可以设置,第一个参数指定分隔符,第二个参数指定
切片长度。
var arr = ['a', 'b', 'c', 'd'];
var str1 = arr.join('-');
console.log(str1); // a-b-c-d
var arr1 = str1.split();
console.log(arr1); // ['a-b-c-d']
var arr2 = str1.split('-');
console.log(arr2); // ['a', 'b', 'c', 'd']
var arr3 = str1.split('-', 2);
console.log(arr3); // ['a', 'b']