编译期类型检查 in ClojureScript
前言
话说"动态类型一时爽,代码重构火葬场",虽然有很多不同的意见(请参考),但我们看到势头强劲的TypeScript和Flow.js,也能感知到静态类型在某程度上能帮助我们写出更健壮的代码(当然要基于充分的单元测试上啦)。
ClojureScript与JavaScript一样采取动态类型,但由于需要通过Google Closure Compiler编译后才能运行,因此我们可以如同JS那样借助GCC的注解来引入编译时类型检查,达到同样静态类型的效果。
配置项目设置
GCC的编译时类型检查仅当optimizations
为simple
或advanced
时有效。我们以:cljsbuild
下的dev配置为例
:cljsbuild
{:builds
[{:id "dev"
:main type-check.core
:output-to "resouces/public/js/type_check.js"
:optimizations :simple
:source-map "resources/public/js/type_check.js.map"
:closure-warnings ;; 设置GCC编译时类型检查
{:check-types :warning ;; 务必设置为warning
:undefined-names :off ;; 屏蔽goog库的异常信息
:externs-validation :off ;; 屏蔽goog库的异常信息
:missing-properties :off ;; 屏蔽goog库的异常信息
}}]}
请注意,:check-types
必须设置为:warning
,若设置为:error
时,就会报Math.imul引发的JSC_DUP_VAR_DECLARATION_TYPE_MISMATCH异常
,导致项目其他代码均不能被编译。希望大神指点迷津~~
注解语法
首先GCC用到的注解语法仅为JSDoc的子集,所以直接看GCC的注解即可,而ClojureScript一般就用如下几个
@private {Type}
标识私有成员,且该成员的数据类型
@type {Type}
标识成员的数据类型
@param {Type} varname Description
标识函数的型参的数据类型,参数名和描述
@return {Type} Description
标识函数返回值的数据类型和描述
@throws {Type}
标识函数可能抛出异常类型
接下来就是重点了,我们写了这么多还不就是想引入数据的类型描述吗?那关键就是上述代码中Type到底应该怎么写了!
1.标量类型number
,string
,boolean
,null
,undefined
注意
一、标量类型默认表示变量或参数的实际值为不可为null(non-nullable)。若要标识为可为null(nullable),那么只需前置一个问号?
即可(?number
,?string
)
2.对象类型Object
,Function
,Number
,String
,Boolean
,Date
和其他Cljs或自定义的对象类型。
注意
一、对于非全限定的对象类型,会自动展开为当前命名空间的类型(如当前命名空间为my-proj.core
,那么MyArray
会展开为my-proj.core/MyArray
)
二、对象类型默认表示变量或参数的实际值可为null(nullable)。若要标识为不可为null(non-nullable),那么只需前置一个感叹号!
即可(如!Object
,!Date
等)
3.组合类型,如(number|string)
,即是实际值可为数字也可为字符串。
4.集合/字典,Array<Type>
表示为数组类型且其元素类型可以继续递归下去,Object<Type>
表示为对象类型且键类型为Type,Object<Type1,Type2
表示为对象类型且键类型为Type1而值类型为Type2
5.函数类型
function(Type1,Type2)
,表示函数含数据类型为Type1和Type2两个形参。
function(Type1,Type2):Type3
,表示函数含数据类型为Type1和Type2两个形参,且返回值类型为Type3。
function(...Type)
,表示函数含数据类型为Type的可变形参,注意可变形参必须作为最后一个形参出现。
function(Type=)
,表示函数含可选的数据类型为Type的形参,注意可选形参后不能声明必填的形参。
注意注意!
- 形参和逗号间千万不要留空格,否则编译时会报警告的哦!
- Type为function()时不能在声明返回值类型,否则编译时辉报警告!
@param {function(*,function(*):number)} 是不允许的
@param {function(*,function(*))} 只能这样写啦
6.什么类型都可以,*
实例
1.封装chrome.runtime.onMessage玩玩
(defn on-msg
"@param {function(*,window.MessageSend,function(*))} handler
@return {null}"
[handler]
(let [this (.. js/chrome -runtime -onMessage)]
(.addListener this
(fn [a b c]
(handler a b c)
true))))
注意:window.MessageSend
既不是GCC内置的类型也不是我们自定义类型,而是外部定义的数据类型,因此我们需要添加externs文件让GCC识别。
因此得到的配置如下
:cljsbuild
{:builds
[{:id "dev"
:main type-check.core
:output-to "resouces/public/js/type_check.js"
:optimizations :simple
:source-map "resources/public/js/type_check.js.map"
:externs ["externs/chrome.js" "externs/chrome_extensions.js"]
:closure-warnings ;; 设置GCC编译时类型检查
{:check-types :warning ;; 务必设置为warning
:undefined-names :off ;; 屏蔽goog库的异常信息
:externs-validation :off ;; 屏蔽goog库的异常信息
:missing-properties :off ;; 屏蔽goog库的异常信息
}}]}
总结
如官网所讲,这部分的内容仍在发展阶段,所以还有很多不完善的地方。不过也不影响我们现在就开始使用,因此良好的代码注释从来都需要的!
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/7625414.html _肥仔John
参考
https://clojurescript.org/reference/compile-time-type-checking
https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler
https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System
https://github.com/google/closure-compiler/wiki/Warnings
欢迎添加我的公众号一起深入探讨技术手艺人的那些事!
如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!