从数组的遍历方式谈谈代码的语义性

一、前言

我之前的一篇文章介绍了几种遍历数组的方式,当然远不止这几种。不过今天想谈的是别的东西,数组遍历的具体方式和用法就不再赘述了。今天,我想以数组遍历方式为切入点,谈一下代码的语义性。当然,这只是个人理解。

首先,代码语义性是什么?从我的角度来说,语义性就是代码本身呈现的、和具体功能无关的、表达代码作用的程度,或者说是代码直观的程度。

别人看一段代码,不看代码具体的执行逻辑和过程,能理解你这段代码的意图越多,你的代码语义性就越强。很多代码规范,有一个作用就是增强代码的语义性。比如类名命名规范、变量命名规范、函数命名规范、模块命名规范等等。如果严格遵守这些规范,那么别人看到你的代码,看属性名或者方法名,就知道这段逻辑的作用是什么。比如,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都适合用于对数组成员进行一个条件判断的场景。

三、结语

其实,数组遍历,用一个方法就能实现所有的需求,只是有些需要额外的变量,有些需要额外执行几次迭代而已。但是,除了这些性能上或者说代码简洁方面的考虑,更重要的,影响我们选择用什么方法的因素,我觉得是代码是否直观的问题,或者说是语义性。

实现功能的下一个阶段是更好的实现功能,不管是从代码角度还是从实现角度,做到更好。

开发能力,见于大,也见于小。实现很难的需求,很重要,组织好代码,也很重要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章