一、前言
我之前的一篇文章介绍了几种遍历数组的方式,当然远不止这几种。不过今天想谈的是别的东西,数组遍历的具体方式和用法就不再赘述了。今天,我想以数组遍历方式为切入点,谈一下代码的语义性。当然,这只是个人理解。
首先,代码语义性是什么?从我的角度来说,语义性就是代码本身呈现的、和具体功能无关的、表达代码作用的程度,或者说是代码直观的程度。
别人看一段代码,不看代码具体的执行逻辑和过程,能理解你这段代码的意图越多,你的代码语义性就越强。很多代码规范,有一个作用就是增强代码的语义性。比如类名命名规范、变量命名规范、函数命名规范、模块命名规范等等。如果严格遵守这些规范,那么别人看到你的代码,看属性名或者方法名,就知道这段逻辑的作用是什么。比如,addCount这个方法名,表示这是一个增加数量的方法,count,表示统计一个东西的数量。
再比如h5新增的一些元素,section、article,header、footer等,一眼看去,就知道这些元素的作用,这就是代码的语义性的一个体现。
js原生提供了很多遍历的方法,在我看来,虽然其实用一种方法就能满足所有的需求,但是应该根据不同的使用需求,选择对应的方法。除了节约性能,更重要的是加强代码的语义性和可读性。
二、示例
以下简单介绍一下数组遍历方式以及它们使用场景的理解
1.原生的for循环和for in循环
const list = [1, 2, 3];
for (let i = 0; i < list.length; i++) {
const item = list[i];
//... do something
}
for循环,性能最强,没有额外的操作,可随时终止,不过写起来最繁琐,可读性稍微差点。所以我觉得是所有遍历方式中用来兜底的,现在用的比较少。另一个优点是可以continue或break,不过因为有了后面出的for of,现在,按照我的理解,只有在同时需要用到item和index,并且还需要根据条件continue或者break的场景里才适合用这个了。它另外一个优势——性能——已经因为越来越强大的js引擎导致被人们越来越少提及。
for in循环一般用来遍历对象的键值对,数组的键就是下标,性能for循环差很多,因为数组内部优化了迭代方式,for in是如果对象的键值对来遍历的。缺点一样,繁琐,可读性较差。这里提一句,因为es6新增的Object.values()、Object.keys()、Object.entries()这三个方法可以把对象转换成对应的数组,所以for in出现的频率也越来越低了。
2.for of循环
const list = [1, 2, 3];
for (const item of list) {
//do something
if (item >= 2) {
break;
}
}
es6新增的方法,因为es6提出了iterator的概念,所以for of应运而生。它基本上就是es6版本的for循环,可以continue和break,有个小小的缺点是没有内置的index,与数组内置的方法相比,写起来还是稍微有一点繁琐。如果你需要按需求终止或跳过循环,且不需要index,那么for of循环是最合适的。
3.forEach
从这里开始,介绍的方法都是Array.prototype上内置的,即每个数组实例都能调用的方法。他们都遵循了函数式编程的理念,把迭代的函数作为参数传递进去,并且为迭代函数提供了迭代项和index,且都不会改变原数组(这不意味着在迭代过程中修改成员也不会改变原数组)。
const list = [1, 2, 3];
list.forEach(item => {
//do something
});
forEach的一个特点是没有返回值,我个人理解为纯操作的一个函数,需要对数组每一项进行操作,且不需要返回值时,可以使用forEach方法。
4.map
const list = [1, 2, 3];
const newList = list.map((item, index) => item * index);
//newList=[0,2,6]
map方法有返回值,返回值是一个新的数组,新数组的每一项成员,都是对应的原数组每一项执行完迭代函数之后的返回值。换句话来说,map这里是映射的意思,一个数组的成员按照相同的规则映射为一个新的数组。所以,如果你需要在不改变原数组时得到一个原有数组的映射,可以使用map方法。
5.filter
const list = [1, 2, 3];
const newList = list.filter((item, index) => item * index >= 4);
//newList=[3]
filter方法有返回值,返回值是一个新的数组。原数组的每一项成员,执行迭代函数的返回值如果为真值,则会将这个成员放进新数组,如果返回值为假值,则不会放入新数组(假值就是和false==判断为true的值,是false、null、undefined、0,'',NaN,真值是除假值外的其他量)。这里filter是过滤的意思,一个数组筛选出所有符合条件的成员。所以,如果你需要在不改变原数组时得到一个原数组的部分成员,那么可以使用filter方法。
6.reduce和reduceRight
const list = [
{
id: 1,
label: "万里归来颜愈少"
},
{
id: 2,
label: "笑时犹带岭梅香"
},
{
id: 3,
label: "试问岭南应不好"
},
{
id: 4,
label: "此心安处是吾乡"
}
];
const res = list.reduce((res, item) => {
res[item.id] = item;
return res;
}, {});
//res={
// '1': { id: 1, label: '万里归来颜愈少' },
// '2': { id: 2, label: '笑时犹带岭梅香' },
// '3': { id: 3, label: '试问岭南应不好' },
// '4': { id: 4, label: '此心安处是吾乡' }
// }
reduce方法有返回值,返回值的结果,我个人理解是原数组每一项成员对一个原始值进行操作。所有成员都执行一次迭代函数之后,原始值就变成了最终的结果。(如果每一轮迭代不会影响初始值,那么为什么不用forEach呢)。具体场景呢,我曾经得到过一个数组,数组的每个成员有个id,为了方便的根据id获取某个成员,我用了reduce方法建立了id和数组成员的映射关系。
reduceRight和reduce的使用方式完全一样,区别是reduceRight是从数组的最后一项成员开始执行迭代函数。
7.find和findIndex
const list = [
{
id: 1,
label: "万里归来颜愈少"
},
{
id: 2,
label: "笑时犹带岭梅香"
},
{
id: 3,
label: "试问岭南应不好"
},
{
id: 4,
label: "此心安处是吾乡"
}
];
const item = list.find(item => item.id === 2);
const index = list.findIndex(item => item.id === 2);
// item={id: 2,label: "笑时犹带岭梅香"}
// index=1
find方法有返回值,返回值的结果是第一个执行迭代函数的返回值为真值的那一项成员。如果数组每一项成员执行完迭代函数的返回值都是假值,那么find方法会返回undefined。所以,如果我们要找到原数组中第一个符合条件的成员,可以使用find方法。
findIndex方法和find方法的原理一样,区别是findIndex方法返回的是第一个符合条件成员的index。如果没有找到符合条件的成员,那么findIndex的返回值是-1。
find和findIndex的一个优点是,确定返回的结果时就会立刻终止循环,所以不需要担心会执行进行额外的循环
8.some和every
const list = [1, 2, 3];
const res = list.some(item => item >= 2);
// res = true
some方法有返回值,如果原数组的每一项成员执行迭代函数的结果,有一项为真值,那么some方法会返回true。反之,如果每一项成员执行迭代函数的返回值都是false,那么some方法的返回值就是false。你可以把每一项成员执行迭代函数的返回值理解成或的关系。
const list = [1, 2, 3];
const res = list.every(item => item >= 2);
// res = false;
every方法有返回值,如果原数组的每一项成员执行迭代函数的结果,有一项为假值,那么every方法会返回false。反之,如果每一项成员执行迭代函数的返回值都是true,那么every方法的返回值就是true。你可以把每一项成员执行迭代函数的返回值理解成且的关系。
some和every方法的一个优点是,只要确定了结果,就会立即返回结果,不需要担心会执行进行额外的循环。
every和some都适合用于对数组成员进行一个条件判断的场景。
三、结语
其实,数组遍历,用一个方法就能实现所有的需求,只是有些需要额外的变量,有些需要额外执行几次迭代而已。但是,除了这些性能上或者说代码简洁方面的考虑,更重要的,影响我们选择用什么方法的因素,我觉得是代码是否直观的问题,或者说是语义性。
实现功能的下一个阶段是更好的实现功能,不管是从代码角度还是从实现角度,做到更好。
开发能力,见于大,也见于小。实现很难的需求,很重要,组织好代码,也很重要。