浅谈两个不推荐的代码习惯

218 阅读8分钟

我正在参加「掘金·启航计划」

概述

事情是这样的,昨天在翻修旧项目的过程中看到了两个非常不推荐的代码习惯,虽然能用,但很容易留坑,而且,奇怪的是,以前带新人的时候,也经常会看到这两种写法,也许是一个通病问题。

不过基本上只要了解过一次,后面就不会犯同样的错误,所以特意整理出来,希望对刚入门的朋友有帮助。

这两个不推荐的代码习惯分别是:

  1. 在 forEach 或 map 中修改原数据
  2. select 下拉选择中的内容位置顺序逻辑强关联

下文将分两个小节来展开介绍

在 forEach 或 map 中修改原数据

“错误”示范

const students = [
  {
    name: 'nick',
    age: 12,
    gender: 'male',
  },
  {
    name: 'jack',
    age: 14,
    gender: 'male',
  },
  {
    name: 'ross',
    age: 14,
    gender: 'female',
  },
];

console.log('students 原始数据', students);

// 直接在 forEach 中修改数组中的数据会导致原本的数据也发生变化,不建议这样做
students.forEach((item) => {
  item.grade = '一年级';

  if (item.name === 'nick') {
    item.age = 18;
  }
});

console.log('students 经过 forEach 后的数据', students);

上述代码中,students 是一个数组,里面每一个项都是对象,然后直接在 students.forEach 中修改各项的数据,就会导致原数据被直接修改,如果下图所示:

本质上这个是 JS 引用类型 数据的共性特点(强烈建议刚入门的朋友深入了解一下 JS基本类型与引用类型的区别,理解透这一点的话,很多奇奇怪怪的边界情况都能无师自通)

另外,这一节的小标题虽然是 “错误”示范,但不是说这种做法就是 100%错误的,一定不能这样做,正如本文标题所述,这是一种 “不推荐” 的代码习惯

不推荐这样做的原因也很简单:这样会修改原本的数据内容,从而引发一些“意外”情况

比如说,上面 students 这个数据是一个公共数据,有很多个地方都需要用到,如果在某个地方执行了上述操作,将会修改原数据,这样其它地方在使用时,拿到的也是被修改后的数据,这也许并不是其它地方在使用这个数据时所期待的,从而就是引发一桩桩“人间悲剧” ╥﹏╥...

还是不理解?我们换一个现实的例子,假设我们去中式餐馆吃饭,看完餐牌后,觉得不合自己口味,然后偷偷把人家中式餐馆的所有餐牌都换了,全部换成西式的。如果真这样的话,估计人家餐馆老板会被气到原地爆炸,其他顾客也会一脸懵逼~

所以说,一般情况下,当我们的代码仅仅只是一个“消费者”的时候,那就建议不要修改原本的数据,安守好自己“消费者”的本分就好

讲到这里,应该不是很绕吧?好,那我们继续 (~ ̄▽ ̄)~

这时候肯定有朋友会问,那有没有可以像开头代码这样做的场景呢?

那肯定是有的,如果我们的代码是这份数据唯一的“生产者”,我们就可以小心地这样做(注意这里两个加粗的关键词 “唯一”“小心地”

还是先讲栗子吧,还是上述中式餐馆的栗子:

  1. “唯一”,强调的是你是这家餐馆唯一的老板的话,当然有权力修改餐牌。但如果是合伙制的话,修改前是不是要问一下合伙人?是不是要一起商量商量?如果在合伙人完全不知道或者不同意的情况下修改餐牌的话,大概率会吵架
  2. “小心地”,强调的是你不能非常鲁莽地修改,总不能现在还有顾客在点餐,就马上拿出一份西式餐牌换点顾客手上原本的餐牌对吧?

我们写代码也是一样的,如果我们确定自己的代码是这份数据唯一的“生产者”,我们才有修改数据的“权力”。另外,在修改时也要留意一下原本这份数据的“消费者们”,看修改后的数据是否会对“消费者们”产生他们“意外”的情况甚至错误的情况,如果没有,就可以直接修改

“正确”示范

const friends = [
  {
    name: 'John',
    age: 26,
    gender: 'male',
  },
  {
    name: 'Ania',
    age: 24,
    gender: 'female',
  },
];

console.log('friends 原始数据', friends);

const newFriends = friends.map((item) => {
  if (item.name === 'John') {
    return {
    ...item,
    age: 30,
    grade: '早毕业了',
  }
  }

  return {
    ...item,
    grade: '早毕业了',
  }
});

console.log('friends 经过 map 后的数据', friends);

console.log('newFriends 的数据', newFriends);

这段代码应该就不用过多阐述了,当我们有需求需要修改原数据 friends 时,不是直接修改,而是通过 friends.map 拷贝一份数据,在拷贝数据上进行修改,不影响原数据

还是上述餐馆的栗子,我们去中式餐馆,可能这家餐馆味道是重庆餐馆,味道偏辣,我们广东人可能接受不了,可以让老板对我们的菜做“广东辣”,这样的话,我们可以接受,而且也不会影响到其他顾客

select 下拉选择中的内容位置顺序逻辑强关联

昨天在修复旧项目的 bug 时,找到了以下这段代码注释,说实话,当时看到这句注释的时候真是哭笑不得 ⊙﹏⊙|||

在感谢前人耐心注释的同时也有着万般无奈 ┭┮﹏┭┮

这份数据的应用场景是一个手机号国家码下拉选择框(额,请忽视里面 newcode 这种不规范的命名方式,旧项目一堆这样的命名习惯,不推荐、不推荐、不推荐,看着难受,但不是我们这次要讲的重点)

错误示范

作为一个下拉选择框,讲道理,里面的内容多与少、位置顺序如何,不应该影响到正常的代码逻辑才对。所以,当看到这句注释的时候,基本上已经锁定了 bug 估计就是这里,大概率是前前人把里面内容的位置顺序跟逻辑进行了强关联,然后某个前人没留意,修改了原有的顺序,然后引发了一连串问题,按这个方向排查了一下,果不其然,确实是这个问题,如下图所示:

我的天呐,下拉选择的 value 绑定的居然是数组的下标顺序~

强调,这是错误做法!

强调,这是错误做法!

强调,这是错误做法!

这一节的小标题 错误示范,我可没有把 错误 二字打上双引号喔,因为个人觉得这种做法是一种不折不扣,真·错误 的做法

再额外提一句,上图中在 map 里面直接给 item 添加一个 no 字段的做法也是有问题的,跟上面我们刚聊完的“在 forEach 中修改数据”的问题原因是一样的,都是改变了原本的数据

let countryList = key.countryList.map((item, index) => {
    item.no = index; // 这个是有问题的,改变了原本的数据
    return item;
});

正确示范

正确示范-演示 Demo

部分代码如下图:

实际上这里并没有什么特殊操作,核心要关注的是:前端维护、发送给后端接口,后端接口返回时的回显数据,都是具有唯一性的 code 字段

这样,无论是下拉选择中的内容数据如何变化(顺序变化、数据增减等),只要我们能确保 code 是唯一的话就不会有问题

上述例子中用的是 vue + elementUI,其 select 组件已经帮我们封装好细节,回显数据时,只需要修改双向绑定的 code 响应式数据就 ok,组件内容会在数组中自动找到对应的项

这些成熟的 UI 框架一般都帮我们做好了封装,不过也不排除有一些没有 UI 框架的组件没法做到上述这种简介的用法。比如说,有些 UI 框架的下拉选项在 change 时只会返回内容数组的下标 index 值,这时我们最好不要头脑一热,把下标 index 传给后端,如果用下标 index 的话就会犯上述 错误示范 中的问题,极容易留坑

正确的做法是,前端维护下标 index 值,通过下标 index,找到内容中的唯一值,比如上述列子中的 code,前后端保存、回显的数据始终都要是这个 code,只是对于这种“不怎么好用”的下拉组件,我们需要手动根据 code 在内容数组中找到其对应的下标 index 值(这里这么繁琐的步骤 vue + elementUI 都帮我们解决了,就一个字,爽!!!)