Javascript是动态类型语言,动态类型并不代表没有类型,它的类型是运行时决定的。阅读代码时,我们往往脑补变量的类型,是数字?还是字符串?很多时候读不懂代码,很大的原因是不能确定当前变量的类型,需要从变量定义处,函数调用处,去获知类型,一来二去阅读代码的思路就被打断了,不得不从头再来分析。这还算是运气好的,有些参数是多种类型,调用的地方又不在阅读的代码中,注释又是模糊不清,只能靠猜测,靠log,靠debugger,真是有苦说不出。
假如每读到一段代码就能明确的获知变量的类型,这不止会节省不少时间,还能开开心心的大呼:"阅读代码使我愉快"
这里我不是要介绍Typescript,Flow这些成熟的工具。我要介绍的是如何写类型才能传递给读者最明确,最关键,最舒心的信息。我会先介绍简单的类型然后接受==--(注:下面的类型全参考Haskell,也可以当做Haskell类型签名的介绍
简单类型
简单的类型不用写注释,一眼就能看出。
let name = "diqye"
let index = 10
let isFoo = true
name 是 String , index 是 Number, isFoo 是Boolean
倘若只是声明不赋值或者赋值为null,需要标明类型(:这里是通过注释来标明
// String
let name
if (...) name = ...
else name = ....
// Number
let foo = null
函数的类型
函数分为两部分,一曰:输入,二曰:输出 即 参数列表和返回值。参数列表用括号括起来,假如只有一个参数可以不用括号,返回值放到->的后面。
// (Number,Number) -> Number
function add (a, b) {
return a + b
}
// Number -> Number
let abs = n => Math.abs(n)
add函数接受两个Number参数,类型和实参一一对应, 返回值Number类型。
没有返回值
其实js中不存在没有返回值的函数,"没有返回值"的函数 返回的是 undefined,但是鉴于undefined太长,不美观,写起来不方便,而又很常见就用 () 代表undefined。
// String -> ()
function mylogic (type) {
...somecode
}
上面这些很简单,注释也很自然,大家也很容易想得到。在简单的场景下即使不写类型注释,也很容易明确类型,下面开始介绍复杂的场景。
复杂的类型
有一些类型它不是单纯的一个值,需要和别的类型组合起来 如 Array,Promise,Object....。数组本身是一个类型,数组内包含的值又是一个类型。这样的类型称之为 带有参数的类型 (捂脸 不用纠结名字
// Array
let arr = []
...somecode
arr = [1,2]
如上面这样写,只能知道 arr是 Array ,并不知道arr里面是什么类型。改为如下就会好很多
// Array Number
let arr = []
...somecode
arr = [1,2]
现在可以通过注释中得知 arr是数组,数组里面是Number。鉴于Array是非常常见的类型可以用[Number]代表是数组类型,里面包含Number类型。
//[Number]
let arr = []
...somecode
arr = [1,2]
Object是通过多个别的类型组合而来。
// {name:String,age:Number}
let user = {name:'diqye',age:26}
Promise是js中内置封装异步操作的类型,在成功的时候通过then方法获取成功的data,在失败的时候通过catch方法获取错误的data。因此Promise内包含两个类型,一个是成功的时候的类型,一个失败的时候的类型 。 写作 Promise Type1 Type2。
// type Data = {name:String,age:Number}
// String -> Promsie Data String
function req(url){
...somecode
return Promise
}
通过类型注释可以明确的知道数据是什么样的,请求成功我可以得到name,age 失败我得到的是一个字符串
类型变量
有的时候类型包含的类型是不明确的,往往需要在调用的时候明确,这时使用类型变量。前面介绍到的类型都是以大写开头的,如果以小写开头就视为类型变量,类型变量代表某一种类型。
// a -> a
function id (b) { return b}
id(1) // 这时 上面的 a 代表Number
id('hello') // 这时 上面的 a 代表String
也就是类型变量在调用的时候确定变量所代表的类型。
下面通过高阶函数介绍,类型变量如何明确的表面一个函数的意图。
高阶函数
函数参数列表中有函数,或者返回一个函数,这样的函数称之为 高阶函数。以常见的forEach,map为例子
// ((a -> ()),[a]) -> ()
function forEach(fn,xs){
xs.forEach.bind(xs)(fn)
}
// ((a -> b),[a]) -> [b]
function map(fn,xs){
return xs.map.bind(xs)(fn)
}
forEach函数的类型注释表明了 forEach接受两个参数 无返回值(返回undefined) 。第一个参数是函数,这个函数入参是 a ,a 是后面数组中的a,无返回。第二个参数是数组。这样在使用forEach的时候就知道 我该传入怎样一个函数,并且根据函数名字也很容易想到是遍历数组调用函数。类型注释吧关键的信息都描述出来了。
map这个函数的类型注释更有意思了,接受 a -> b 的函数,一个[a]的数组 返回[b],再明白不过的标明了将[a] 通过 a -> b 的函数 变为 [b]了。
不知道你注意到了没有,我在描述map函数的时候 直接使用类型注释(如 a -> b)描述了,比用文字 (接受一个a返回b这样的一个函数)简洁多了,也易读多了。
Currying函数
关于什么是Currying函数 我这一篇文章有介绍
【js基础】js优美的实现自动Currying(柯理化),这里就不再多说了。
// Number -> Number -> Number
let add = curry(function(a,b){return a+ b})
没有新的规则,只需要把括号去掉就足以描述它了。有趣的是我们还可以进行推导。
// (a -> b) -> [a] -> [b]
let map = curry(function map(fn,xs){
return xs.map.bind(xs)(fn)
})
// [Number] -> [Number]
let allAdd1 = map(add(1))
allAdd1这个函数是手工推导出来的。 数组中所有的值加1 ,还有比map(add(1)) + 类型注释 描述更简洁的方法吗?
最后
这里的类型注释参考的是Haskell,在Haskell里叫类型签名。虽只介绍了最基本的层面,但在js中能满足80%的场景,也没有深入的必要,毕竟JS中没有那么强大的类型系统。
初来简书,先试下水。 /捂脸