-
Array API
- 静态方法
- 数组首尾元素处理
- 数组遍历(重要)
- 数组查找
- 数组过滤(重要)
- 数组合并
- 数组删除与截取
- 数组排序
-
String API
- 字符串查找与匹配
- 字符串替换
- 字符串合并
- 字符串首尾空格去除
- 字符串大小写转化
- 字符串删除与截取(重要)
Array API
数组方法在JS中是一个重要的知识点。本文将重点讲解一些方法的使用细节和使用场景。以下是对数组方法的分类
静态方法
方法 | 说明 |
---|---|
Array.from() |
从数组类对象或可迭代对象创建一个新的 Array 实例。 |
Array.isArray() |
如果参数是数组则返回 true ,否则返回 false 。 |
应用场景:Array.from()
这个方法常用于转化NodeList
类数组对象,让DOM集合也能拥有数组方法。
数组首尾元素处理
方法 | 说明 |
---|---|
Array.prototype.pop() |
从数组中移除最后一个元素并返回该元素。 |
Array.prototype.push() |
在数组末尾添加一个或多个元素,并返回数组新的 length 。 |
Array.prototype.shift() |
从数组中移除第一个元素并返回该元素。 |
Array.prototype.unshift() |
在数组的前面添加一个或多个元素,并返回数组新的 length 。 |
应用场景:我们可以利用上述方法能够非常简单的模拟栈和队列这两种数据结构,如下:
let arr = [ 1, 2, 3, 4, 5 ]
// 栈数据结构,后进先出。
arr.pop(5) // 出栈操作 [ 1, 2, 3, 4 ]
arr.push(5) // 入栈操作 [ 1, 2, 3, 4, 5 ]
// 队列数据结构,先进先出。
arr.shift(1) // 出队操作 [ 2, 3, 4, 5 ]
arr.push(1) // 入队操作 [ 1, 2, 3, 4, 5 ]
其中pop
和push
方法分别对应栈的出栈与入栈操作。shift
和push
方法分别对应队列的出队和入队操作。
当然,我们也可以进一步基于以上方法利用面向对象编程分别封装栈和队列这样的数据结构进行操作。
数组遍历(重要)
方法 | 说明 |
---|---|
Array.prototype.forEach() |
对调用数组中的每个元素调用函数。 |
Array.prototype.map() |
返回一个新数组,其中包含对调用数组中的每个元素调用函数的结果。 |
应用场景:以上两个方法都十分常用。其中NodeList
自带forEach
方法,在DOM编程中可以应用。 此外,在vue中我们常常会设计这样一个数据结构用于v-for循环。
let arr = [
{ id: 0, name: "小红", age: 22 },
{ id: 1, name: "小华", age: 18 },
{ id: 2, name: "小明", age: 24 },
]
arr.forEach((element) => {
console.log(element.name) // 结果:"小红" "小明" "小华"
})
let result = arr.map((element) => { // 方法返回一个新数组,我们使用result变量接收
return element.id * 2 // 返回的element会作为result数组中的元素
})
console.log(result) // 结果:[ 0, 2, 4 ]
使用以上方法可以非常简便的访问数组中每个对象的属性,以此对每个对象属性值进行统计、运算和赋值等操作。
数组查找
方法 | 说明 |
---|---|
Array.prototype.find() |
返回数组中满足提供的测试函数的第一个元素的值,如果没有找到合适的元素,则返回 undefined 。 |
Array.prototype.findIndex() |
返回数组中满足提供的测试函数的第一个元素的索引,如果没有找到合适的元素,则返回 -1 。 |
Array.prototype.indexOf() |
返回在调用数组中可以找到给定元素的第一个(最小)索引。 |
应用场景:需要根据条件获取索引或值的元素。一般根据返回值用于判断数组内是否存在符合条件的元素。
数组过滤(重要)
方法 | 说明 |
---|---|
Array.prototype.includes() |
确定调用数组是否包含一个值,根据情况返回 true 或 false 。 |
Array.prototype.some() |
如果调用数组中至少有一个元素满足提供的测试函数,则返回 true 。 |
Array.prototype.every() |
如果调用数组中的每个元素都满足测试函数,则返回 true 。 |
Array.prototype.filter() |
返回一个新数组,其中包含调用所提供的筛选函数返回为 true 的所有数组元素。 |
Array.prototype.reduce() |
对数组的每个元素(从左到右)执行用户提供的 “reducer” 回调函数,将其简化为单个值。 |
应用场景:这里重点讲解filter
和reduce
两个最常用的方法。
-
reduce
方法应用广泛,可用于数组去重、数组扁平化等操作。基本使用方法如下:
reduce((previousValue, currentValue) => { /* … */ } , initialValue)
其中reduce
方法接收两个参数:一个是回调函数,一个是初始值。若已设定初始值initialValue
,回调函数中的第一个参数previousValue
等于initialValue
,第二个参数为数组的第一个元素。在reduce
方法遍历过程中,回调函数的第二个参数currentValue
将会按顺序访问数组元素。第一个参数previousValue
则为上一次return
的结果。来看一个数组去重的应用例子:
let a = [1, 2, 2, 3, 3, 3]
function unique(arr) {
return arr.reduce((pre, cur) => {
return pre.includes(cur) === true ? pre : pre.concat(cur) // concat会返回一个新数组
// 判断上一次return的结果(pre)是否存在相同元素。
// 如果存在pre不变直接return返回;若不存在pre合并当前元素后return返回
}, []) // [] 空数组作为初始值
}
console.log(unique(a)) // 结果:[ 1,2,3 ]
-
filter
方法主要应用于有条件需要进行数据筛选的数组。还是上上面的数组结构,如下:
let arr = [
{ id: 0, name: "小红", age: 22 },
{ id: 1, name: "小华", age: 18 },
{ id: 2, name: "小明", age: 24 },
]
// 这里做一个筛选,我们只要大于18岁的人作为新数组数据
// 使用filter方法
let filterResult = arr.filter(element => element.age > 18) // 不用花括号会直接return表达式
console.log(filterResult)
// 结果:[{ id: 0, name: "小红", age: 22 }, { id: 2, name: "小明", age: 24 }]
// 使用map方法
let mapResult = arr.map(element => element.age > 18) // 不用花括号会直接return表达式
console.log(mapResult)
// 结果:[ true, false, true ]
这里做一个比较:filter
相比map
的区别。当数组内元素的不符合条件时,map
方法内部不符合条件的元素仍然会根据return
的内容占据原来的位置;而filte
r方法则根据return
的条件(true
或false
)去掉不符合的元素,返回符合条件元素组成的数组。
数组合并
方法 | 说明 |
---|---|
Array.prototype.join() |
将数组的所有元素连接为字符串。 |
Array.prototype.concat() |
返回一个新数组,该数组由被调用的数组与其它数组或值连接形成。 |
说明:concat
方法可以进行数组合并。如下:
let arr = [].concat([1, 2], [3, 4])
console.log(arr)
// 输出结果:[1, 2, 3, 4]
值得注意的点:
-
concat
方法不会改变数组,而是返回一个新数组,需要使用变量接收这个新数组。 -
concat
方法传入的参数如果是嵌套数组会展开其第一层进行合并。如下:
let arr = [].concat(1, 2, [3, 4, [5, 6, [7, 8]]])
console.log(arr)
// 输出结果:[1, 2, 3, 4, Array(3)]
应用场景:我们利用上面concat
方法传入参数是嵌套数组会展开一层进行合并的特点,再加上扩展运算符和递归的思想可以非常巧妙的实现数组扁平化。如下:
// 三行代码实现数组扁平化
function flatten(arr, deep) {
return deep > 0 ? flatten([].concat(...arr), deep - 1) : arr
}
// 参数说明:arr = 原数组,deep = 展开的深度(从0开始,完全展开可以设为Infinity)
let a = [1, 2, [3, 4, [5, 6, [7, 8]]]] // 原数组
let result = flatten(a, 3) // 传入数组执行方法,展开深度为3
console.log(result)
// 输出结果:[1, 2, 3, 4, 5, 6, 7, 8]
过程分析如下:
原始数组:[1, 2, [3, 4, [5, 6, [7, 8]]]]
扩展运算符将原始数组展开后:1, 2, [3, 4, [5, 6, [7, 8]]]
concat
方法的传入参数合并后:[1, 2, 3, 4, [5, 6, [7, 8]]]
重复上面的展开与合并过程,可以深度拍平数组。
数组删除与截取
方法 | 说明 |
---|---|
Array.prototype.slice() |
提取调用数组的一部分并返回一个新数组。 |
Array.prototype.splice() |
从数组中添加和/或删除元素。 |
说明:我们重点关注slice
和splice
的参数说明。如下:
let a = [1, 2, 3, 4, 5, 6]
let b = a.slice(2, 4) // 提取位置2到位置4的元素,返回一个新数组用变量b接收
console.log(b) // [3, 4]
参数说明:slice(start, end)
方法可接收两个参数,一般用于提取数组中的元素。
-
start
是数组开始提取的位置,若是负值则代表倒数第几位开始(等价array.length - n
),若start
超出数组长度则会返回空数组。 -
end
代表数组终止提取的位置,若end
被省略或end
超出数组长度slice()
方法都会从start
开始提取到数组末尾。 -
slice()
会提取原数组中索引从begin
到end
的所有元素(包含begin
,但不包含end
)作为新数组返回。
let a = [1, 2, 3, 4, 5, 6]
a.splice(2, 4, 7) // 从位置2开始删除4个元素,删除后在原数组位置2添加元素7
console.log(a) // [1,2, 7]
参数说明:splice(start, deleteCount, item1, item2, itemN)
方法可接收三个以上参数,可用于删除数组中的元素,
-
start
:代表数组开始修改的位置,若是负值则代表倒数第几位开始(等价array.length - n
),若start
超出数组长度则以末尾开始修改。 -
deleteCount
:代表从start
开始要移除的元素个数(含第start
位), 若deleteCount
大于start
之后的元素的总数,则从start
后面的元素都将被删除。 -
item1,item2...
:代表删除后从原数组start
位置开始要添加的数据,会在删除后不指定则splice()
方法只会删除数组元素。
数组排序
方法 | 说明 |
---|---|
Array.prototype.sort() |
对数组的元素进行排序并返回该数组。 |
Array.prototype.reverse() |
反转数组中元素的顺序。 |
sort
方法利用原地算法对数组进行排序。简单来说就是在不开阔新的空间情况下,只能在原数组内用相互替换的方法交换数组内元素进行排序。比如冒泡排序、插入排序和希尔排序等空间复杂度为O(log n)
的排序算法。当然我们最关心的还是它的用法。sort
方法接收一个回调函数fn
,回调函数里可以传入两个参数a
和b
。排序过程中回调函数的返回值可以有以下情况:
-
fn(a, b) > 0
:a
在b
之后 -
fn(a, b) < 0
:a
在b
之前 -
fn(a, b) === 0
:a
和b
位置保持不变
let strs = ['Axios', 'Candy', 'Mike', 'Alice']
let a = strs.sort()// 没有回调函数,默认按元素转化成后字符串的各个字符的Unicode代码点进行排序
console.log(a) // ['Alice', 'Axios', 'Candy', 'Mike']
// 字母相同的元素('Axios'和'Alice')会利用元素下一个字符串的Unicode代码点继续进行排序
let nums = [2, 1, 4, 3, 5, 6]
let b = nums.sort((a, b) => a - b) // 存在回调函数,a-b为升序
console.log(b)
let c = nums.sort((a, b) => b - a) // 存在回调函数,b-a为降序
console.log(c)
let objs = [
{ id: 0, name: "小红", age: 22 },
{ id: 1, name: "小华", age: 18 },
{ id: 2, name: "小明", age: 24 },
]
let d = objs.sort((a, b) => a.age - b.age) // 也可以利用对象属性值进行排序(这里是升序)
console.log(d) // 按年龄大小对元素排序
reverse()
方法用于数组逆序,如下:
let nums = [1, 2, 3, 4, 5, 6]
nums.reverse()
console.log(nums) // [6, 5, 4, 3, 2, 1]
特别注意:所有返回新数组的数组方法都不会改变原有数组,因此要用一个新变量接收返回的新数组。
返回新数组的方法总结如下:
Array.prototype.map()
Array.prototype.filter()
Array.prototype.concat()
Array.prototype.slice()
Array.prototype.sort
String API
在网络上我们每天都需要利用字符进行交流,间接说明我们应该掌握字符串的处理方式。这是前端开发中不可或缺的技能。以下是字符串方法的分类:
字符串查找与匹配
方法 | 说明 |
---|---|
String.prototype.indexOf() |
并返回指定子字符串第一次出现的索引。 |
String.prototype.match() |
用于将正则表达式 regexp 与字符串匹配。 |
String.prototype.search() |
搜索正则表达式 regexp 和调用字符串之间的匹配项。 |
我们重点关注一下indexOf
方法。indexOf
方法可以搜索子字符串第一次出现的索引。根据传入参数的不同搜索的结果分别以下有三种情况:
let a = "abc";
a.indexOf("c") // 正确传参,输出结果:2
a.indexOf() // 不传参数,输出结果:"undefined"
a.indexOf("d") // 传入参数错误或子字符串在字符串中找不到,输出结果:-1
match
方法传入一个正则表达式作为参数,与search
方法的区别是:match
方法会返回结果分为两种情况:若正则表达式设为全局匹配g
会返回一个匹配的子字符串结果(数组形式),若未设定全局匹配则会返回一个有特定结构的结果数组。如果都没匹配成功则返回null
。search
方法则会返回首次匹配字符串的索引,不匹配返回-1
。
let a = "AbCdEfG"
let regex1 = /[A-Z]/g // 设定全局匹配
a.match(regex1) // 输出结果:['A', 'C', 'E', 'G']
let regex2 = /[A-Z]/ // 为设定全局匹配
a.match(regex2) // 输出结果:['A', index: 0, input: 'AbCdEfG', groups: undefined]
字符串替换
方法 | 说明 |
---|---|
String.prototype.replace() |
用于使用 replaceWith 替换出现的 searchFor 。 |
应用场景:可以做字符替换,replace
方法接收两个参数。第一个参数可以是正则表达式或字符串,第二个参数是要替换的内容。replace
方法在字符串匹配成功后,会进行替换。替换如下:
let str = 'My name is Jelly';
str.replace('Jelly', 'Mary') // 输出结果: 'My name is Mary'
字符串合并
方法 | 方法 |
---|---|
String.prototype.concat() |
合并两个(或更多)字符串的文本并返回一个新字符串。 |
字符串的concat
方法跟数组的concat
方法操作基本相同。用于字符串的合并操作。
字符串首尾空格去除
方法 | 说明 |
---|---|
String.prototype.trim() |
修剪字符串开头和结尾的空格。 |
String.prototype.trimStart() |
修剪字符串开头的空格。 |
String.prototype.trimEnd() |
修剪字符串结尾的空格。 |
应用场景:去掉字符串的首尾空格,如下:
let str = ' Hello, World ';
str.trim(); // 输出结果:'Hello, World'
字符串大小写转化
方法 | 说明 |
---|---|
String.prototype.toLowerCase() |
字符串中的字符将转换为小写。 |
String.prototype.toUpperCase() |
字符串中的字符将转换为大写。 |
应用场景:以上的字符串方法可以应用于字母的大小写替换,可以来做一道题。实现小驼峰式命名法:将一个英语短语转换为小驼峰式命名。例如:first name
=> firstName
function toConvertCase(str) {
let result = '' // 存储新字符串,用于合并新的字符
let count = 0 // 记录初始状态,因为第一个字母需要小写
let space = false // 记录字符状态,是否出现已经特殊字符
for (let i = 0; i < str.length; i++) {
if (str[i].search(/^[A-Za-z]+$/) === 0) {
if (count === 0 && space === false) {
count++
} // 此时count恒等于1,根据下面的三元表达式第一次出现的字母为小写
result = space === true && count !== 0
? result.concat(str[i].toUpperCase())
: result.concat(str[i].toLowerCase())
space = false // 第一次出现的字母和首字母大写后的字母都为小写,设为false
} else {
space = true
// 出现特殊字符space设为true。下一次若是字母则三元表达式的值为true,新字符串合并首字母大写。
}
}
return result
}
// 短语输入测试
let a = toConvertCase('!@# fiRst %^&*( nAmE !@#$ ')
let b = toConvertCase('423432 SEcOnd@3423423 NaME')
let c = toConvertCase('@ThIrd-3NAmE')
console.log(a) // 输出结果:firstName
console.log(b) // 输出结果:secondName
console.log(c) // 输出结果:thirdName
只要输入有特殊字符间隔的短语就能返回小驼峰式命名,容错率还是非常高的。获取的字符串特征如下:
- 短语之间会存在特殊字符间隔,利用这个作为实现切入点。
- 第一个单词的首字母为小写,后面的单词首字母都为大写。
可以根据上面的字符串特征进行解题,不过上面的使用好像有点超纲了。我们还是重点关注它怎么用就行了。
字符串删除与截取(重要)
方法 | 说明 |
---|---|
String.prototype.slice() |
提取字符串的一部分并返回一个新字符串。 |
String.prototype.substring() |
返回一个新字符串,其中包含来自(或之间)指定索引(或多个索引)的调用字符串的字符。 |
字符串的slice
方法跟数组的slice
方法操作基本相同。接收两个参数为开始和结束位置(不包括)进行字符提取。我们重点关注substring
方法,这个是最常用的字符串截取操作。substring
方法接收两个参数,这里暂且设为start
和end
两个参数,分别代表开始和结束位置(不包括)的字符。使用规则如下:
-
start === end
返回一个空字符串 - 省略
end
参数,substring
方法直接提取到末尾 - 任一参数小于
0
或为NaN
,会当作0
进行截取 - 任一参数大于字符串长度,则会截取到
array.length
-
start > end
会进行参数调换再截取字符串
let a = "abcdefg"
a.substring(2, 2) // 输出结果:''
a.substring(2) // 输出结果:'cdefg'
a.substring(2, NaN) // 输出结果:'ab'
a.substring(8, 2) // 输出结果:'cdefg'
a.substring(4, 2) // 输出结果:'cd'
参考
MDN-Array
MDN-String