编译期类型检查 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库的异常信息
}}]}
总结
如官网所讲,这部分的内容仍在发展阶段,所以还有很多不完善的地方。不过也不影响我们现在就开始使用,因此良好的代码注释从来都需要的!
参考
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
- 几种简单的文本数据预处理方法
- Fiddler中显示IP方法
- readlink: command not found 解决方案
- Java 并发编程系列: CountDownLatch (上厕所的案例)
- 详解ANGULAR2组件中的变化检测机制(对比ANGULAR1的脏检测)
- 如何快速查看github代码库中第一次commit的记录
- 更换包管理工具npm为yarn
- 【精选】使用Cryptory分析影响加密货币价格的因素(区块链系列3)
- React -- 组件间通信
- 图片和视频防盗链简单介绍
- 对比cp和scp命令 将数据从一台linux服务器复制到另一台linux服务器
- laravel—用Migration的操作数据库
- 有货移动Web端性能优化探索实践
- webpack打包速度和性能再次优化
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 在CentOS VPS上通过SSH安装 MySQL的办法图解
- 详解PHP 7.4 中数组延展操作符语法知识点
- linux下安装boost库的完整步骤记录
- Python图像处理二值化方法实例汇总
- 利用Python实现斐波那契数列的方法实例
- Python如何实现大型数组运算(使用NumPy)
- 因str_replace导致的注入问题总结
- laravel框架中间件 except 和 only 的用法示例
- 浅析Python 多行匹配模式
- PHP命名空间定义与用法实例分析
- php array_chunk()函数用法与注意事项
- Laravel如何同时连接多个数据库详解
- PHP面向对象程序设计中的self、static、parent关键字用法分析
- php实现简单的守护进程创建、开启与关闭操作
- 关于CentOs系统自带python和yum卸载后的解决办法