JavaScript 须要检查变量类型吗

2018.3.1更:javascript

有赞·微商城部门招前端啦,最近的前端hc有十多个,跪求大佬扔简历,我直接进行内推实时反馈进度,有兴趣的邮件 lvdada#youzan.com,或直接微信勾搭我 wsldd225 了解跟多前端

有赞开源组件库·zanUIvue


javascript做为一门动态类型语言,具备很高的动态灵活性,当定义函数时,传入的参数能够是任意类型。但咱们在实际编写函数逻辑时默认是对参数有必定要求的。这也容易致使预期参数与实际参数不符的状况,从而致使bug的出现。本文在这个层面探讨javascript检查参数的必要性。java

为何要进行类型检查?

从两点常见的场景来看这个问题:webpack

  • 程序中指望获得的值与实际获得的值类型不相符,在对值进行操做的时候程序报错,致使程序中断。

举个咱们最多见的调用服务端ajax请求取到返回值进行操做的例子:es6

ajax('/getContent', function (json) {
	
	// json的返回数据形式
	// {data: 18}
	var strPrice = (data.data).toFixed(2);
})
复制代码

若是服务端返回的数据形式以及返回的data必定是number类型,咱们这样操做确定没有问题。web

可是若是服务端返回的数据发生了变化,返回给咱们的形式变成了:ajax

{
	data: '18.00'
}
复制代码

而咱们在js中并无对变量作检测,就会致使程序报错。编程

'18.00'.toFixed(2) // Uncaught TypeError: "18.00".toFixed is not a function
复制代码
  • 跟第一点类似也是指望获得的值与实际获得的值类型不相符,可是对值操做不会报错,js利用隐式类型转换获得了咱们不但愿获得的值,这种状况会加大咱们对bug的追踪难度。

举一个也是比较常见的例子:json

/**
* input1 [number]
* input2 [number]
* return [number]
**/
function sumInput (input1, input2) {
	return input1 + input2;
}
复制代码

sumInput方法的两个入参值可能来自外界用户输入,咱们没法保证这是一个正确的number类型值。

sumInput(1, ''); // return '1'
复制代码

sumInput方法原本指望获得number类型的值,可是如今却获得了string类型的'1' 。虽然值看起来没有变化,可是若是该值须要被其余函数调用,就会形成未知的问题。

再举一个罕见的例子:

parseInt()方法要求第一个参数是string类型,若不是,则会隐式转换成string类型。

parseInt(0.0000008) // 8
复制代码

匪夷所思吧?咱们预计这个方法的结果应该是0,但结果倒是8。在程序中咱们没法捕获这个错误,只能隐没在流程中,最终的计算结果咱们也没法确保正确。

缘由是parseInt(0.0000008)会变成parseInt("8e-7"),结果输出8

类型检查原则

因为js语言的动态性,以及自己就没有对类型作判断的机制,咱们是否须要对全部变量值进行类型判断?这样作无疑增长了编码的冗余度,且无需对变量类型作检查也正是动态语言的一个优点。

那为了不一些由此问题带来的bugs,咱们须要在一些关键点进行检查,而关键点更多的是由业务决定的,并无一个统一的原则指导咱们哪里必须进行类型判断。

但大致趋势上能够参考如下我总结的几点意见。

1、「返回值」调用外部方法获取的值须要对类型作判断,由于咱们对方法返回的值是有指望值类型,可是却不能保证这个接口返回的值一直是同一个类型。

换个意思讲就是咱们对咱们不能保证的,来源于外部的值都要保持一颗敬畏之心。这个值可能来自第三方工具函数的返回值,或者来自服务端接口的返回值,也多是另外一位同事写的抽离公共方法。

2、「入参」在书写一个函数并给外部使用的时候,须要对入参作较严格的类型判断。

这里强调的也是给外部使用的场景,咱们在函数内部会对入参作不少逻辑上的处理,若是不对入参作判断,咱们没法确保外部使用者传入的究竟是什么类型的参数。

3、「自产自销」除了以上两类与外部交互的场景,更多须要考虑的是咱们在编写业务代码时,“自产自销”的变量该如何处理。

解释一下“自产自销”的意思,在编写业务代码时,咱们会根据业务场景定义不少函数,以及会调用函数取返回值。在这个过程当中会有入参的状况,而这些参数彻底是本身编写本身使用,在这种对代码相对了解的前提下无条件的进行变量类型判断无疑会增长编码的复杂度。

在实际编码中咱们更多的会使用强制类型转换[Number String Boolean]对参数进行操做,转换成咱们指望的类型值。具体的方式会在下一章节阐述。

如何处理和反馈变量类型与指望不符的状况

首先谈谈如何判断变量类型,咱们可使用原生js或者es6的语法对类型进行准确判断,但更多的可使用工具库,相似于lodash。包含了经常使用的isXXX方法。

  • isNumber
  • isNull
  • isNaN
  • ...

对变量进行类型判断后,咱们该如何进行处理及反馈?

  • 「静默处理」只对符合类型预期的值进行处理,不符合预期的分支不作抛错处理。这样作能够防止程序报错,不阻塞其余与之无关的业务逻辑。
if (isNumber(arg)) {
	xxx
} else {
	console.log('xxx 步骤 获得的参数不是number类型');
}
复制代码
  • 「抛错误」不符合预期的分支作抛错处理,阻止程序运行。
if (isNumber(arg)) {
	xxx
} else {
	throw new TypeError(arg + '不是number类型');
}
复制代码
  • 「强制转换」将不符合预期的值强制转换成指望的类型。
if (isNumber(arg)) {
    (arg).toFixed(2);
} else {
    toNumber(arg).toFixed(2);
}

//可是强制转换更多的在咱们对变量类型教有掌控力的前提下使用,因此咱们不会进行判断,直接在逻辑中进行强制转换。
toNumber(arg).toFixed(2);

复制代码

以上三种途径是咱们在对变量进行类型判断后积极采起反馈的通用作法。那么结合上一章提到的3大类型检查原则,咱们分别是采用哪一种作法?

「返回值」调用外部函数、接口获得的参数该如何处理反馈?

对于由外部接口获得的值,咱们无法确保这个类型是永恒的。因此进行类型判断颇有必要,可是到底是采用「静默处理」、「抛错误中断」仍是「强制转换类型」呢?这里仍是须要根据具体场景具体业务采用不一样的方式,没有一个恒定的解决方案。

看个例子:

// 业务代码入口
function main () {
	
	// 监控代码 与业务无关
	(function () {
		var shopList = getShopNameList(); // return undefined
		Countly.push(shopList.join()); // Uncaught TypeError: Cannot read property 'join' of undefined
	})()

	// 业务代码
	todo....
}
复制代码

上述例子中的咱们调用了一个外部函数getShopNameList, 在对其返回值进行操做时与主要业务逻辑无关的代码块出错,会直接致使程序中断。而对shopList进行判断后静默处理,也不会影响到主要业务的运行,因此这种状况是适合「静默处理」的。静默处理的最大优点在于能够防止程序报错,可是使用的前提是这步操做不会影响其余相关联的业务逻辑。

若是被静默处理的值与其余业务逻辑还有关联,那么整条逻辑的最终值都会受到影响,可是咱们又静默掉了错误信息,反而会增长了寻找bug的难度。

// 业务代码入口
function main () {
	
	// 监控代码 与业务无关
	(function () {
		var shopList = getShopNameList(); // return undefined
		if (isArray(shopList)) {
			Countly.push(shopList.join());
		}
	})()

	// 业务代码
	todo....
}
复制代码

固然除了「静默处理」外咱们还能够选择「强制转换」,将返回值转换成咱们须要的值类型,完成逻辑的延续。

// 业务代码入口
function main () {
	
	// 监控代码 与业务无关
	(function () {
		var shopList = getShopNameList(); // return undefined
		Countly.push(isArray(shopList) ? shopList.join() : '');
	})()

	// 业务代码
	todo....
}
复制代码

「入参」在书写一个函数并给外部使用的时候,对入参该如何处理反馈?

当咱们写一个函数方法提供给除本身以外的人使用,或者是在编写前端底层框架、UI组件,提供给外部人员使用,咱们对入参(外部使用者输入)应该要尽量的检查详细。由于是给外部使用,咱们没法知道业务场景,因此使用「静默处理」是不合适的,咱们没法知道静默处理的内容与其余业务逻辑有否有耦合,既然静默了最终仍是会致使bugs出现,还不如直接「抛错误」提醒使用者。

在第三方框架中,都会自定义一个相似于warn的方法用于抛出变量检查的不合法结果。并且为了防止检查代码的增长而致使的线上代码量的增长,一般检查过程都会区分本地开发环境和线上生产环境。

// 代码取自vue源码
  if (process.env.NODE_ENV !== 'production' && isObject(def)) {
    warn(
      'Invalid default value for prop "' + key + '": ' +
      'Props with type Object/Array must use a factory function ' +
      'to return the default value.',
      vm
    )
  }
复制代码

这段判断脚本结合webpack构建生产环境的代码时就会被删除,不会增长生产环境的代码量。

vue框架的组件系统中对组件传参的行为vue在框架层面上就支持了检查机制。若是传入的数据不符合规格,vue会发出警告。

Vue.component('example', {
  props: {
    // 基础类型检测 (`null` 意思是任何类型均可以)
    propA: Number,
    // 多种类型
    propB: [String, Number],
    // 必传且是字符串
    propC: {
      type: String,
      required: true
    },
    // 数字,有默认值
    propD: {
      type: Number,
      default: 100
    },
    // 数组/对象的默认值应当由一个工厂函数返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})
复制代码

由于咱们编写vue组件也会提供给他人是使用,也属于与外部交互的场景。Vue在框架层面集成了检查功能,也方便了咱们开发者再手动检查参数变量了。

「自产自销」除了以上两类与外部交互的场景,更多须要考虑的是咱们在编写业务代码时,“自产自销”的变量该如何处理?

外部交互的场景,咱们对入参以及返回值具备不可控性,但对于开发者开发业务时的场景,传参时,或者是函数返回值,都是咱们本身定义的,相对具备很强的可控性。

规定参数类型是string字符串时,咱们大几率不会传入一个数组,并且变量的值也不会由外部环境的变化而变化(ajax返回的参数,外部接口返回的参数,类型可能会变)。

那么剩下的状况大部分会集中在js标量基础类型值。

  • 规定传入number 13,咱们传入了string '13'
  • 规定传入boolean true,咱们传入了真值 '123'
  • ...

针对这种状况,咱们对入参的值具备必定的可预期性,预期类型可能不一样,为了程序的健壮性,可读性更高,更容易使协做同窗理解,咱们通常采用「强制转换」将值转换成咱们指望的类型。即便「强制转换」的过程当中程序发生了报错从而中断,这也是在调试过程当中产生程序中断问题,也能更好的提早暴露这个问题,避免在线上环境发生bugs。

function add(num1, num2) {
	return (toNumber(num1) + toNumber(num2))
}
add('123', '234');
复制代码
  • toInteger
  • toNumber
  • toString
  • toSafeInteger
  • !!(toBoolean)

隐式强制类型转换会踩到哪些坑?

由于js会默默的进行隐式类型转换,因此多数坑都是发生在对值的操做过程当中发生了隐式类型转换。

另外类型转换越清晰,可读性越高,更容易理解。

  • string型数字调用toFixed()方法报错
'123'.toFixed(2) // Uncaught TypeError: "123".toFixed is not a function
复制代码
  • + 法中有字符串出现则操做变成字符串拼接
function add(num1, num2) {
	return num1 + num2
}
add(123, ''); //  return string '123'
复制代码
  • 当咱们使用==进行值相等判断的时候两边的值会进行隐式强制类型转换,而转换的结果每每不尽人意。
function test(a) {
	if (a == true) { // 不推荐
		console.log('true')
	} else {
		console.log('false')		
	}
}
test('22')  // 'false'

// 缘由
'22' == true

两边都会发生隐式强制转换,'22' --> 22 , true --> 1, 
所以 22 == 1  // false
复制代码
function test(a) {
	if (a == '') {
		console.log('true')
	} else {
		console.log('false')		
	}
}
test(0)  // 'true'

// 缘由
0 == ''

字符串会发生隐式类型转转 '' --> 0
所以 0 == 0 // true

相同的场景还有

[] == 0 // true
[] == '' // true
复制代码

因此当咱们进行相等判断时涉及到[], 0, '', boolean,不该该使用==,而应该采用===,杜绝发生隐式强制类型转换的操做。

全局环境如何作到变量的类型检查?

依靠开发者进行参数变量的类型检查,很是考验js开发者的js基础功,尤为在团队协做下很难作到完美的类型检查。vue2的源码开发使用了flow协助进行类型检查。

Flow 是一个facebook出品静态类型检测工具;在现有项目中加上类型标注后,能够在代码阶段就检测出对变量的不恰当使用。Flow 弥补了 JavaScript 天生的类型系统缺陷。利用 Flow 进行类型检查,可使你的项目代码更加健壮,确保项目的其余参与者也能够写出规范的代码;而 Flow 的使用更是方便渐进式的给项目加上严格的类型检测。

// @flow
function getStrLength(str: string): number{ 
    return str.length; 
}
getStrLength('Hello World'); 
复制代码

另外还有微软出品的TypeScript,采用这门js超集编程语言也能开发具备静态类型的js应用。

  • TypeScript 增长了代码的可读性和可维护性,能够在编译阶段就发现大部分错误,这总比在运行时候出错好。
  • TypeScript 是 JavaScript 的超集,.js 文件能够直接重命名为 .ts 便可
  • 有必定的学习成本,须要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型

总结

本文从3个类型检查原则「返回值」「入参」「自产自销」为出发点,分别阐述了这三种状况下的处理方法「静默处理」「抛错误」「强制转换」。本文阐述的是一种思路,这三种处理方法其实在各个原则中都会使用,最重要的仍是取决于业务的需求和理解。可是尽可能的对变量类型作检查是没有错的!

本文来自二口南洋,有什么须要讨论的欢迎找我。

相关文章
相关标签/搜索