尽管从第一次遇到空值引起的bug开始,我就一直要求自己注意空值,但还是经常犯这样的错误,JS中的空值真的需要多加注意。这里说的空值包括
undefined
和null
为什么JS容易出现空值bug?
首先JS是一个动态类型语言,与之相对的是静态类型语言如Java。在Java中要定义数据模型意味着定义一个类——JavaBean,无论这个数据模型的结构多么简单。在JS中就简单的多:
var auth = {
visible: true,
editable: true
}
这种灵活的方式带来了很大的便捷,但这种便捷是有代价的。JS并不能保证所有的auth对象是同样的结构,auth.visible
并不一定是布尔型,有可能是undefined
或者其它类型的数据。尽管定义一个JavaBean有些麻烦,Java中不会出现这样的情况——你可以确定一个auth
对象一定有一个布尔型的visible
属性。
空值bug会以哪些形式出现?
有的时候定义数据时会默认
{}
或者undefined
就是{visible: false, enbale: false}
,或者{name: 'afei'}
就是{name: 'afei': phone: ''}
。在使用数据时很容易遗漏这种隐藏逻辑,从而引发bug。而且这种空值只在少部分情况下出现,很容易躲过测试。
1.
function fn (obj) {
console.log(obj[key].id);
}
这里
obj
或者obj[key]
为空都会导致报错。
2.
function fn (authArray) {
authArray.forEach(function (i, auth) {
console.log(auth.visible);
})
}
这种形式就更具迷惑性,你很容易就觉得
authArray
里面肯定都是auth
吧,既然是auth
肯定有visible
属性吧。然而并没有人能保证这一点,authArray
里面可能有别的数据:空值或者没有visible
属性的auth
对象。
3.
function fn (authIdArray, authMap) {
authIdArray.forEach(function (i, authId) {
var auth = authMap[authId];
console.log(auth.visible);
})
}
同样,看到
authIdArray
和authMap
,很容易觉得它们一定是一一对应的,应该可以取到一个auth
对象。
总而言之,JS并不能保证数据的结构化,而开发过程中很容易默认取到的是结构化数据。
怎么解决
遇到空值bug有两种解决方案。
- 在取值的地方加判断
function fn (auth) { var val = obj[key]; if (val) { console.log(val.id); } }
还有一种写法:
function fn (obj) { console.log(obj[key] && obj[key].id); }
- 在变量声明和赋值的地方保证数据结构一致
var auth = { visible: booleanVal || false } authArray.push(authObj || {}); authMap[id] = authObj || {};
如果auth的某个属性还不是基础类型,可能更麻烦一些——不能用
{}
来代替auth
。
第一种方法改动起来更简单,但是第二种方法能保持数据结构一致,有利于代码的长期维护。一种比较中庸的方法是:在私有方法中声明和赋值时保证数据的结构一致,在公共方法中接收的变量则检查数据结构(JS不区分公共和私有方法,一个约定俗成的做法是私有方法加上下划线前缀)。
从TypeScript得到的启示
TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个严格超集,并添加了可选的静态类型和基于类的面向对象编程。所谓可选的静态类型可以理解为灵活范式——本来是无范式的。TypeScript可以声明参数或属性的数据类型,在编译时进行类型检查,也可以通过any类型来跳过类型检查。 使用TypeScript后IDE的补全和跳转可以像静态语言一样,对开发者更加友好,数据类型引起的bug也会大大减少。从使用者评价来看,大型项目中使用TypeScript有助于提高开发效率和减少bug。
fx-code工程引入TypeScript的障碍有两点:一是工程中没有实现模块化而是AllInOne模式,不能在一个组件中引入另外一个组件,TypeScript也就没办法发挥作用。二是遗留代码需要编写大量的声明文件,而且需要开发者转换静态类型语言的思维。
在引入之前或者不引入的话,我们也可以通过注释来起到申明数据类型的作用。
/**
* @param fields {Array<Field>}
* @interface
* Field {
* name: string,
* text: string,
* enbale: boolean | undefined,
* layout: 6 | 12
* }
* @return void
*/
function fn (fields, fieldAuth) {
// ...
}
对于可能为undefined
和{}
的变量的注释尤其要详细。
原文地址:https://www.cnblogs.com/wangziqiang123/p/11632063.html