js类型代替注释者优美也

作者: Rezeroer | 来源:发表于2017-12-16 19:33 被阅读222次
    类型注释

    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中没有那么强大的类型系统。

    初来简书,先试下水。 /捂脸

    相关文章

      网友评论

        本文标题:js类型代替注释者优美也

        本文链接:https://www.haomeiwen.com/subject/jxhewxtx.html