深入数组拓展应用一
内容大纲:
-
数组高频方法
-
实战演练
-
数组查找方法
数组高频方法
首先来回顾一下数组有关的高频方法,这些方法都是原生 JavaScript 为我们提供了的,熟练的掌握这些方法可以让我们做开发时达到事半功倍的效果。
slice( ) 和 splice( )
slice( ) 方法用于截取数组。该方法可以接收一个或者两个参数,代表返回项的起始和结束位置。
-
1 个参数:那就代表起始位置,返回从指定的起始位置到数组末尾的所有项目
-
2 个参数:那就代表从指定的起始位置到指定的末尾位置之间的项,但是不包括结束位置的项目
注:slice( ) 方法不会影响原始数组
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 一个参数
var i = arr.slice(3);
console.log(i); // [ 4, 5, 6, 7, 8, 9, 10 ]
console.log(arr); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
// 两个参数
var j = arr.slice(2, 6);
console.log(j); // [ 3, 4, 5, 6 ]
console.log(arr); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
如果传入的是负数,则用数组长度加上该数来确定相应的位置。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 一个参数
var i = arr.slice(-3);// 等价于slice(7)
console.log(i); // [ 8, 9, 10 ]
// 两个参数
var j = arr.slice(-6, -2); // 等价于slice(4,8)
console.log(j); // [ 5, 6, 7, 8 ]
// 不满足条件返回空数组
var k = arr.slice(-2, -6); // 等价于slice(8,4)
console.log(k); // []
splice( ) 这个方法非常的厉害,可以实现对数组的 3 种类型的操作:删除,插入和替换,相当于是增删改操作都可以用这个方法来实现。
删除:可以删除任意数量的元素,只需要指定 2 个参数:要参数的第一项位置和要删除的项数
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 从下标为3的元素开始删除,删除5个元素
// 将删除的元素返回给i
var i = arr.splice(3, 5);
console.log(i); // [ 4, 5, 6, 7, 8 ]
console.log(arr); // [ 1, 2, 3, 9, 10 ]
插入:可以向任意位置插入任意数量的元素。只需要提供 3 个参数:起始位置,0(要删除的项目),要插入的项目。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 从下标为3的元素之前开始插入
var i = arr.splice(3, 0, "red", "blue");
console.log(i); // []
console.log(arr);
// [ 1, 2, 3, 'red', 'blue', 4, 5, 6, 7, 8, 9, 10 ]
替换:替换的原理在于插入的时候同时进行删除,这样就实现了替换功能。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 从下标为3的元素之前开始插入
// 插入多少,刚好就删除多少
var i = arr.splice(3, 2, "red", "blue");
console.log(i); // [ 4, 5 ]
console.log(arr);
// [ 1, 2, 3, 'red', 'blue', 6, 7, 8, 9, 10 ]
数组和字符串相互转换
join( ) 是将数组转为字符串,可以传入分隔符作为参数。
var arr = [1, 2, 3];
var str = arr.join("");
console.log(str); // 123
var str2 = arr.join(",");
console.log(str2); // 1,2,3
split( ) 是将字符串转为数组,传入参数指明以什么作为分隔符。
var str = "Hello";
var arr = str.split("");
console.log(arr); // [ 'H', 'e', 'l', 'l', 'o' ]
var arr2 = str.split("l");
console.log(arr2); // [ 'He', '', 'o' ]
数组重排序方法
重排序涉及到两个方法:reverse( ) 和 sort( )
reverse( ) 用于反转数组项的顺序,注意使用该方法时会改变原来数组的顺序,而不是返回一个副本。
var arr = [1, 2, 3, 4, 5];
console.log(arr.reverse()); // [ 5, 4, 3, 2, 1 ]
console.log(arr); // [ 5, 4, 3, 2, 1 ]
sort( ) 是按照升序排列数组每一项。
var arr = [0, 12, 3, 7, -12, 23];
console.log(arr.sort());
// [ -12, 0, 12, 23, 3, 7 ]
可以看到,我们调用 sort( ) 方法以后排序并没有按照正确的升序来进行排序,原因是因为默认排序方式是根据字符编码的顺序进行排序。
如果想要按照数字的大小来进行排序,那么需要传入 sortby 参数。
arr.sort(sortby)
sortby 参数是一个可选参数,用来规定如何进行排序,该参数必须是一个函数。该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:
- 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
- 若 a 等于 b,则返回 0。
- 若 a 大于 b,则返回一个大于 0 的值。
var arr = [0, 12, 3, 7, -12, 23];
console.log(arr.sort(function (a, b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
}));
在上面的代码中,我们根据 a 和 b 之间的大小比较来返回 1 和 -1 或者 0,然后实际上只要是正值和负值即可。
在后面学习了 ES6 中的箭头函数后,上面的代码可以直接简化为:
var arr = [0, 12, 3, 7, -12, 23];
console.log(arr.sort((a, b) => a - b));
最后需要注意的是,reverse( ) 和 sort( ) 方法,返回值是经过排序之后的数组。
位置方法
JavaScript 还为数组提供了位置方法:indexOf( ) 和 lastIndexOf( )
这两个方法都接收两个参数:要查找的项目和查找的起点位置索引。区别在于一个是从数组开头开始找,一个是从数组末尾开始找。如果没找到就返回 -1。
var arr = ["H", "e", "l", "l", "o"];
console.log(arr.indexOf("l")); // 2
console.log(arr.lastIndexOf("l")); // 3
console.log(arr.indexOf("z")); // -1
还需要注意的是,这两个方法进行查找时使用的是全等进行比较。
var arr = ["1", "2", "3"];
console.log(arr.indexOf(1)); // -1
自定义查找
JavaScript 中提供了 2 个和上面位置方法很相似的:find( ) 和 findIndex( )
该两个方法都接受一个回调函数,第一个是返回数组中符合回调函数要求的第一个值,第二个是返回数组中符合回调函数的第一个值的下标。
var arr = [3, 10, 23, 20];
console.log(arr.find(function(item){
return item >= 18;
})); // 23
console.log(arr.findIndex(function(item){
return item >= 18;
})); // 2
迭代方法
这些方法都需要用户传入一个回调函数,之后这些方法会去迭代数组的每一项,并且将每一项应用到回调函数中。
- every( )
- some( )
- filter( )
- forEach( )
- map( )
every( ) 是对数组的每一项运行给定的函数,如果该函数每一项都返回 true,则返回 true。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 将数组的每一项传入到回调函数,如果每一项返回 true,那么最终返回 true
var i = arr.every(function (item) {
if (item % 2 == 0) {
return true;
} else {
return false;
}
});
console.log(i); // false
与 every( ) 比较相似的是 some( ),该方法可以对数组的每一项运行指定的函数,如果该函数只要有一项返回 true 则返回 true。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 将数组的每一项传入到回调函数,如果有一项返回 true,那么最终返回 true
var i = arr.some(function (item) {
if (item % 2 == 0) {
return true;
} else {
return false;
}
});
console.log(i); // true
filter( ) 中的 filter 是过滤的意思,这个方法会返回一个数组,数组里面返回过滤过的元素。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 将数组的每一项传入到回调函数,然后将返回为 true 的项目组成一个数组
var i = arr.filter(function (item) {
if (item % 2 == 0) {
return true;
} else {
return false;
}
});
console.log(i); // [ 2, 4, 6, 8, 10 ]
forEach( ) 方法在前面介绍数组遍历的时候,就已经见到过了。该方法就是简单的将数组每一项传入到函数,然后执行该函数里面的代码。需要注意一下的是,该回调函数没有返回值。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 将数组的每一项传入到回调函数,然后执行回调函数里面的操作
var i = arr.forEach(function (item) {
console.log(item);
});
console.log(i); // undefined
map( ) 方法是对数组的每一项运行回调函数。最终返回一个数组,这个数组是每次调用函数后的运行结果。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 将数组的每一项传入到回调函数,然后将返回的结果组成一个新的数组返回
var i = arr.map(function (item) {
if (item % 2 == 0) {
return true;
} else {
return false;
}
});
console.log(i);
// [ false, true, false, true, false, true, false, true, false, true ]
实战演练
上面我们已经介绍了很多数组的方法,但是并不是说上面的那些方法就是 JavaScript 数组中所有的方法,仅仅是高频方法而已。
如果想了解 JavaScript 中数组所有的方法,可以参阅 MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array
下面我们在掌握了上面的方法的基础上,来做一些实战演练。
- 字符串反转,例如 “abcd123” 变为 “321dcba”
var str = "abcd123";
console.log(str.split("").reverse().join(""));
- 查询子串首次出现的位置,例如字符串 “abccbaxzabcasdfaxzffgvb” 中查找子串 “axz” 的结果为 5
var str = "abccbaxzabcasdfaxzffgvbasdaxzqwe";
console.log(str.indexOf("axz"));
升级难度:查找所有的 “axz”,将它们的下标保存到一个数组里面
var str = "abccbaxzabcasdfaxzffgvbasdaxzqwe";
var substr = "axz";
/**
* @param {string} haystack
* @param {string} needle
* @return {array}
*/
var strStr = function (haystack, needle) {
// 当查找空串时
if (needle === "") return [];
// 查找到的索引数组
let result = [];
// 遍历字符串
for (let i = 0; i < haystack.length;) {
// 当查找 haystack 中有一个字符相同于 needle 中第一个字符且截取 needle 字符串长度后跟 needle 字符串比较
if (haystack[i] === needle[0] && haystack.substr(i, needle.length) === needle) {
result.push(i);
// 移动 i 下标到查找的位置最后一个字符位置
i = i + needle.length;
} else {
i++
}
}
// 没有查找到返回 []
return result;
};
console.log(strStr (str, substr)); // [ 5, 15, 26 ]
- 判断一个数组是否为对称(回文)数组,对称数组的格式为 [ a, b, c, b, a ]、[ a, b, c, c, b, a ]
var arr1 = ['a', 'b', 'c', 'b', 'a'];
var arr2 = ['a', 'b', 'c', 'c', 'b', 'a'];
var arr3 = ['a', 'b', 'c', 'd', 'c', 'f'];
function isPalindrome(arr) {
var str1 = arr.join("");
var str2 = arr.reverse().join("");
return str1 === str2;
}
console.log(isPalindrome(arr1)); // true
console.log(isPalindrome(arr2)); // true
console.log(isPalindrome(arr3)); // false
数组查找方法
查找方法在原生的 JavaScript 中已经提供了。
- indexOf( )
- lastIndexOf( )
- find( )
- findIndex( )
但是数组的查找一直是面试中比较爱考察的一个点。数组的查找其实并不是仅仅局限于 JavaScript 这门语言,任何有数组这种数据结构的语言,都会涉及到查找的问题。
常见的查找方法有 2 种:
- 顺序查找
- 二分查找
顺序查找
顺序查找通常从数组或列表的第一个元素开始,逐一访问所有项目直至找到满足条件的值并返回索引位置。
如果查找到数组或列表的结尾仍未找到满足条件的值则返回 -1。
function sequentialSearch(arr, target) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] === target) {
return i;
}
}
return -1;
}
二分查找
二分查找又被称之为折半查找。二分查找的基本思路如下:
在有序数组中,取中间数值作为比较对象,若给定值与中间数值相等,则查找成功。
注意:一定要谨记,二分查找是应用在有序的数组中。
若给定值小于中间数值,则在中间数值的左半区继续查找。若给定值大于中间数值,则在中间数值的右半区继续查找。
不断重复上述过程,直到查找成功,或所查找的区域无记录,查找失败。
使用二分算法,每轮循环必然将查找区域缩减一半。
算法使用 (left + right) / 2 来计算中间索引值,最初的 left 为 0 而 right 为 array.length - 1。记住整数除法会舍掉小数部分,因而 5 / 2 的结果是 2。
如果中间索引取到的元素值正是待查找值,则查找结束并返回中间索引。如果待查找值比中间索引的元素值小,right 会被设为 middle - 1,然后在左侧区域重复查找过程。如果待查找值比中间索引的元素值大,left 会被设为 middle + 1,然后在右侧区域重复查找过程。当区域左边界大于右边界时,查找也结束,返回表示未找到的 -1。
下面是二分查找和顺序查找在效率上的对比:
![binarySearch](https://i-blog.csdnimg.cn/blog_migrate/7150d530cc2078212ec3563bd0dd95c8.gif)
可以看出 binary search 在查找数字 37 时只需 3 次,而 sequential search 查找 37 时需要 11 次。
下面是二分查找的 JavaScript 实现版本。
非递归版本:
function binarySearch(arr, target) {
// 初始索引开始位置和结束位置
var left = 0;
var right = arr.length - 1;
while (left <= right) {
var mid = parseInt((left + right) / 2); // 找到中间下标
if (target < arr[mid]) {
right = mid - 1; // 修改有边界
} else if (target > arr[mid]) {
left = mid + 1; // 修改左边界
} else {
return mid; // 要找的数就是这个数,返回其下标
}
}
// 如果在循环内没有找到查找的 target(left <= right) 的情况则返回 -1
return -1;
}
var arr = [1, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59];
console.log(binarySearch(arr, 29)); // 9
递归版本:
function binarySearch(arr, target, left = 0, right = arr.length - 1) {
if (left > right) {
return -1;
}
var mid = parseInt((left + right) / 2);
if(target < arr[mid]){
return binarySearch(arr, target, left, mid - 1);
} else if(target > arr[mid]){
return binarySearch(arr, target, left + 1, right);
} else {
return mid;
}
}
var arr = [1, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59];
console.log(binarySearch(arr, 53)); // 15
-EOF-