2

使用reduce时,避免对accumulator执行副作用

const nextItems = items.reduce((acc, curr) => {
  if (!acc.includes(curr)) {
    // 副作用
    acc.push(curr);
  }
  return acc;
}, []);

console.log('nextItems', nextItems);

去掉副作用

const items = ['张三', '李四', '张三'];

const nextItems = items.reduce((acc, curr) => {
  // 没有副作用
  return !acc.includes(curr) ? [...acc, curr] : [...acc];
}, []);

console.log('nextItems', nextItems);

优化对一份数据的多次遍历

const task = [
    { name: 'xx', finished: false },
    { name: 'xxx', finished: false }
];
const unfinishedTasks = tasks.filter(item => !item.finished);
const finishedTasks = tasks.filter(item => item.finished);

使用reduce来改进:

const { unfinishedTasks, finishedTasks } = tasks.reduce(
  (acc, task) => {
    if (task.finished) {
      acc.finishedTasks.push(task);
    } else {
      acc.unfinishedTasks.push(task);
    }
    return acc
  },
  { unfinishedTasks: [], finishedTasks: [] }
);

去掉副作用代码:

const { unfinishedTasks, finishedTasks } = tasks.reduce(
  (acc, task) => {
    return task.finished
      ? { ...acc, finishedTasks: [...acc.finishedTasks, task] }
      : { ...acc, unfinishedTasks: [...acc.unfinishedTasks, task] };
  },
  { unfinishedTasks: [], finishedTasks: [] }
);

对find的结果进行判空

const items = [
  {
    id: 1,
    name: 'Ben',
    age: 25,
  },
  {
    id: 2,
    name: 'Lily',
    age: 20,
  },
];

const targetItem = items.find((item) => item.age === 50);
console.log(targetItem.name);
// Error: Cannot read properties of undefined (reading 'name')

上面的代码中,我们在find的返回值上直接访问属性,会有可能导致异常。

因为在没有找到目标时,find的返回值会是undefined,在undefined上访问对象,就会抛出异常。

所以,遵循简单的策略,永远对find的返回值进行判空,避免意外情况。

const targetItem = items.find((item) => item.age === 50);
console.log(targetItem?.name);
// undefined

利用map与filter组合过滤

目标:
剔除users不存在于permissions中的用户数据,并且将permissionscodes写入users

// 用户列表数据
const users = [
  {
    id: 1,
    name: 'Ben',
    age: 25,
    codes: [],
  },
  {
    id: 2,
    name: 'Lily',
    age: 20,
    codes: [],
  },
];

// 用户的权限code数据
const permissions = [
  {
    id: 1,
    codes: ['create', 'delete'],
  },
  {
    id: 3,
    codes: ['create', 'delete'],
  },
];

先给出一个符合直觉的版本

方案1:

  1. 遍历users,检查每个user是否存在于permissions,得到hasPermissonUsers
  2. 对上面的hasPermissonUsers进行遍历,从permissions中取出每个usercodes
const adminUsers = users
  .filter((user) => permissions.find((permission) => permission.id === user.id))
  .map((user) => ({
    ...user,
    codes: permissions.find((permission) => permission.id === user.id)?.codes ?? [],
  }));

上面的代码很好的完成了任务,但是还有一点优化的空间。
在上面的代码中,filtermap内部都对permissions进行了一次遍历,有没有办法避免呢?

方案2:

  1. 遍历users,获取每个user对应的permission,将获取到的permission.codes存到当前user中,将无匹配结果的数据作为null返回
  2. users进行一次真假值检查,即可过滤掉不存在于permissions的用户
const betterAdminUsers = users
  .map((user) => {
    // 用target来实现2个目的
    const target = permissions.find((permission) => permission.id === user.id);

    // 1 获取permission中的codes
    if (target) {
      return {
        ...user,
        codes: target.codes,
      };
    }
    // 2 用于给后面的filter过滤掉不存在于permissions的用户
    return null;
  })
  .filter(Boolean);

方案2中,我们减少了一次find查询,而且代码变得更简单了。

关键点就是,在map内部,将本次对permissions的查询结果通过某种方式传递给了filter,避免了filter内部再次permissions对进行查询。

针对方案2的更新:
这种在map里面干两件事的代码,违反了单一职责,感觉没有必要,增加了阅读者的心智负担。

这里对方案1进行了一点小改造,这样map里面保证了单一职责,它只做了数据转存这一件事。然后filter也只做了过滤这一件事。

const adminUsers = users
  .map((user) => {
    const target = permissions.find((permission) => permission.id === user.id);
    return {
      ...user,
      isHasPermission: !!target,
      codes: target?.codes ?? [],
    };
  })
  .filter((user) => user.isHasPermission);

热饭班长
3.7k 声望434 粉丝

先去做,做出一坨狗屎,再改进。