避免恼人的空值之reduce

在平时的项目里,相信不少小伙伴应该都会碰到 Uncaught TypeError: Cannot read property 'name' of undefined 这种错误吧?按照正常的简约写法,直接获取嵌套属性的值,一旦中间甚至刚开始的某个值就是 undefined,控制台会报错,渲染模板也会直接失败。那么,有没有方法可以避免这种情况的发生呢?

最笨的逐级&&

为了避免上面的情况,有的小伙伴就会直接用 && 的方式,也就是如下形式的代码来避免程序报错

if (list && list[index] && list[index].content && list[index].content.length > 0) {
    // 其他代码
}
复制代码

嵌套的层次越深,需要的代码也就越多。

而且这样处理的话,程序确实是不会报错了,但是总觉得有点麻烦,要是我因为记性不好(其实就是懒)少写了一部分的话,有时候不就跟没写一样吗?

angular的 ?. 操作符

angular的 ?. 操作符算得上是一个骚操作了,只要在 模板中加上 ?. 操作符,就可以避免因为 null 或者 undefined 导致的模板渲染错误了。

The safe navigation operator ( ?. ) and null property paths

The Angular safe navigation operator (?.) is a fluent and convenient way to guard against null and undefined values in property paths. Here it is, protecting against a view render failure if the currentHero is null.

翻译过来大概意思就是:

安全导航操作符(?.)和 null 属性路径 angular的安全导航操作符(?.)是一种流畅且方便的方法,可以防止属性路径中的 null 和 undefined 值,如果 currentHero 为 null,则保护视图防止其呈现失败。

当使用 ?. 操作符的时候,The current hero's name is {{currentHero?.name}},即使currentHero 的值是 null 或者 undefined,该部分也只是显示为空白,而不会报错。

如果是常规写法,The current hero's name is {{currentHero.name}},js就会跑出 reference error, TypeError: Cannot read property 'name' of null in [null].

angular还有很多强大的地方,我在这里就不一一列举了。要不是公司的项目规模太小,其实我还是挺想尝试使用 angular 来进行项目开发的。

不得不说,这种处理方式挺优雅的,和平时的写法并没有太大的差异,只需要多加一个 ? 就可以解决问题了。在 vue 和 react,我们暂时是用不上了,那就从函数方面入手吧。

简单粗暴的 reduce

先来看看一开始的需求吧,获取嵌套数据中的某个值,既然是嵌套数据,那么获取数据的时候其实也是一层一层展开的,那么我们就可以在展开的过程中把 undefined 或 null 设置成一个空对象,这么一来就算是 undefined 或 null,获取之后的值也不会抛出 Error TypeError,而只会获取到一个空值。

先简单介绍一下 数组的 reduce 函数的用法吧,

Array.prototype.reduce = function(iterator, initValue) {
    
}
// iterator 迭代函数,(accumulator, currentValue, currentIndex, array)
// accumulator 累加器,即函数上一次调用的返回值。第一次的时候为 initialValue || arr[0]
// currentValue 数组中函数正在处理的的值。第一次的时候initialValue || arr[1]
// currentIndex 数组中函数正在处理的的索引
// array 函数调用的数组

// initValue 虽然不传也行,但是如果数组类型和返回类型不同,我建议最好还是传一个默认值,避免报错
复制代码

函数的形式大概定义成:

// @params data 原始值,第一个值
// @params keys 之后的键名,以 , 隔开,省事,分开写的话还得多写不少引号
function getSafeReduce(data: any, keys: string | number): any
复制代码

初步阶段

为了防止传入的值为空,一定要对 data 和 keys 的值进行判断。

首先判断 data 是否有值,如果不存在,应赋予一个默认值。

其次判断 keys 是否为合法参数,主要判断是否为数字或者字符串,若不符合条件直接返回一个空对象。之后要对 keys 进行 split 操作,因为这个 字符串 才有的方法,为了避免输入数字时报错,还要将 keys 转为 字符串,这个方式就比较多了。

  1. 直接使用String构造一个字符串对象 String(keys)
  2. 利用 + 操作符的特性 '' + keys
  3. 使用ES6的模板字符串 `${keys}`

想用哪种就用哪种吧,我个人还是比较推荐后两种。

function isExist(data) {
    return data !== undefined && data !== null;
}
function isNothing(data) {
    return !isExist(data);
}
function isVaild(data) {
    return typeof data === 'string' || typeof data === 'number';
}

function getSafeReduce(data, keys) {
  if (isNothing(data)) {
    return defaultValue;
  }
  return isVaild(keys) && `${keys}`.split(",").reduce((item, key,) => {
    return item[key] || {}
  }, data) || {};
}
// 测试
getSafeReduce(null, 's,1')
复制代码

进阶阶段

之前的代码其实已经可以达到一定的效果了,但是如果某一部分出现 undefined 或 null 的话,返回的结果就是 空对象,我们一般是想要返回一个null,或者是空字符串之类的,总之应该是自定义的,那么函数定义就应该稍微改变一下,增加一个参数来设置返回默认值了。

// @params data 原始值,第一个值
// @params keys 之后的键名,以 , 隔开,省事,分开写的话还得多写不少引号
// @params defaultValue: any,结果为空时的返回值
function getSafeReduce(data: any, keys: string | number, defaultValue: any): any
复制代码

数组的 reduce 方法第三个参数是数组的当前索引index,第四个参数是数组本身arr,我们可以通过 index === arr.length - 1来判断是否为最终目标值。如果目标值不存在,则设置为默认值。

function getSafeReduce(data, keys, defaultValue,) {
  if (isNothing(data)) {
    return defaultValue;
  }
  return isVaild(keys) && `${keys}`.split(",").reduce((item, key, i, arr) => {
    let result = item[key.trim()];
    if (isNothing(result)) {
      result = (i === arr.length - 1) ? defaultValue : {};
    }
    return result;
  }, data);
}
复制代码

简化阶段

上一步的 if 语句还可以再进行简化,也就是 reduce 函数可以改为:

let result = item[key.trim()];
result = isExist(result) ? result : ((i === arr.length - 1) ? defaultValue : {});
return result;
复制代码

上面的代码其实还可以再简化一下,最终就成为了:

function getSafeReduce(data, keys, defaultValue,) {
  if (isNothing(data)) {
    return defaultValue;
  }
  return isVaild(keys) && 
  `${keys}`.split(",").reduce((item, key, i, arr) => (isExist(item[key.trim()]) ? item[key.trim()] : (i === arr.length - 1) ? defaultValue : {}), data);
}
复制代码

总结

简化的代码不一定就是最好的,从可读性和可维护性考虑的话,倒数第二段代码我觉得应该算是达到一个相对平衡的代码。

不带调试的完整代码如下

function isExist(data) {
    return data !== undefined && data !== null;
}
function isNothing(data) {
    return !isExist(data);
}
function isVaild(data) {
    return typeof data === 'string' || typeof data === 'number';
}

function getSafeReduce(data, keys, defaultValue,) {
  if (isNothing(data)) {
    return defaultValue;
  }
  data = isExist(data) ? data : {};
  return isVaild(keys) && 
  `${keys}`.split(",").reduce((item, key, i, arr) => {
      let result = item[key.trim()];
      result = isExist(result) ? result : ((i === arr.length - 1) ? defaultValue : {});
      return result;
  }, data);
}
复制代码

如果是发布到生产的话,上面代码已经可以达到要求了,但是如果是本地开发进行调试的话,我们既想让模板正常渲染,又想知道哪里出问题了,那就需要再增加一个调试的选项了。

带调试的完整代码如下

function isExist(data) {
    return data !== undefined && data !== null;
}
function isNothing(data) {
    return !isExist(data);
}
function isVaild(data) {
    return typeof data === 'string' || typeof data === 'number';
}

function getSafeReduce(data, keys, defaultValue, debug) {
  if (isNothing(data)) {
    debug && (console.error('传入的第一个参数为空', data));
    return defaultValue;
  }
  data = isExist(data) ? data : {};
  return isVaild(keys) && 
        `${keys}`.split(",").reduce((item, key, i, arr) => {
           let result = item[key.trim()];
           if (isNothing(result)) {
             result = (i === arr.length - 1) ? defaultValue : {};
             debug && (console.error(item, `当前数据不存在键:${key}`));
           }
           return result;
        }, data);
}
复制代码

链接

github: getSafe (带调试的版本)

npm安装:

npm i -S mpd-util 
import { getSafe } from 'mpd-util'
console.log(getSafe({}, 's, 1', 1))
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值