两年多前知道cljs的存在时十分兴奋,但由于工做中根本用不上,国内也没有专门的职位因而搁置了对其的探索。而近一两年来又刮起了函数式编程的风潮,恰逢有幸主理新项目的前端架构,因而引入Ramda.js来疗藉心中压抑已久的渴望,谁知一发不可收拾,因而抛弃全部利益的考虑,遵循心里,好好追逐cljs一番:D cljs就是ClojureScript的缩写,就是让Clojure代码transpile为JavaScript代码而后运行在浏览器或其余JSVM上的技术。因为宿主环境的不一样,所以只能与宿主环境无关的Clojure代码能够在JVM和JSVM间共享,而且cljs也未能彻底实现clj中的全部语言特性,更况且因为JSVM是单线程所以根本就不须要clj中STM等特性呢…… transpile为JS的函数式编程那么多(如Elm,PureScript),为何偏要cljs呢?语法特别吧,有geek的感受吧,随心就好:)html
本文将快速介绍cljs的语言基础,你们能够直接经过clojurescript.net的Web REPL来练练手!前端
首先介绍一下注释的写法,后续内容会用到哦!python
; 单行注释 ;; 函数单行注释 ;;; macro或defmulti单行注释 ;;;; 命名空间单行注释 (comment " 多行注释 ") #! shebang至关于;单行注释 #_ 注释紧跟其后的表达式, 如: [1 #_2 3] 实际为[1 3],#_(defn test [x] (println x)) 则注释了成个test函数
; 空值/空集 nil ; 字符串(String) "String Data Type" ; 字符(Char) \a \newline ; 布尔类型(Boolean),nil隐式类型转换为false,0和空字符串等均隐式类型转换为true true false ; 长整型(Long) 1 ; 浮点型(Float) 1.2 ; 整型十六进制 0x0000ff ; 指数表示法 1.2e3 ; 键(Keyword),以:为首字符,通常用于Map做为key :i-am-a-key ; Symbol,标识符 i-am-symbol ; Special Form ; 如if, let, do等 (if pred then else?) (let [a 1] expr1 expr2) (do expr*)
; 映射(Map),键值对间的逗号禁用于提升可读性,实质上可移除掉 {:k1 1, :k2 2} ; 列表(List) [1 2 3] ; 矢量(Vector) '(1 2 3) ; 或 (list 1 2 3) ; 集合(Set) #{1 2 3}
在任何Lisp方言中Symbol做为标识符(Identity),凡是标识符均会被限制可以使用的字符集范围。那么合法的symbol需遵照如下规则:编程
[0-9:]
[a-zA-Z0-9*+-_!?|:=<>$&]
:
以:
为首字符则解释为Keyword数组
cljs中每一个symbol不管是函数仍是绑定,都隶属于某个具体的命名空间之下,所以在每一个.cljs
的首行通常为命名空间的声明。浏览器
(ns hello-world.core)
文件与命名空间的关系是一一对应的,上述命名空间对应文件路径为hello_word/core.cljs
、hello_word/core.clj
或hello_word/core.cljc
。 .cljs
文件用于存放ClojureScript代码 .clj
文件用于存放Clojure代码或供JVM编译器编译的ClojureScript的Macro代码 .cljc
文件用于存放供CljureScript自举编译器编译的ClojureScript的Macro代码数据结构
要调用其余命名空间的成员,必需要先将其引入架构
;;; 命名空间A (ns a.core) (defn say1 [] (println "A1")) (defn say2 [] (println "A2")) ;;;; 命名空间B,:require简单引入 (ns b.core (:require a.core)) (a.core/say1) ;-> A1 (a.core/say2) ;-> A2 ;;;; 命名空间C,:as别名 (ns b.core (:require [a.core :as a])) (a/say1) ;-> A1 (a/say2) ;-> A2 ;;;; 命名空间C,:refer导入symbol (ns b.core (:require [a.core :refer [say1 say2]])) (say1) ;-> A1 (say2) ;-> A2
cljs中默认采用不可变数据结构,所以没有变量这个概念,取而代之的是"绑定"。ide
; 声明一个全局绑定 (declare x) ; 定义一个没有初始化值的全局绑定 (def x) ; 定义一个有初始化值的全局绑定 (def x 1)
注意:cljs中的绑定和函数遵循先声明后使用的规则。函数式编程
; 编译时报Use of undeclared Var cljs.user/msg (defn say [] (println "say" msg)) (def msg "john") (say) ; 先声明则编译正常 (declare msg) (defn say [] (println "say" msg)) (def msg "john") (say)
函数的一大特色是:必定必然有返回值,而且默认以最后一个表达式的结果做为函数的返回值。
; 定义 (defn 函数名 [参数1 参数2 & 不定数参数列表] 函数体) ; 示例1 (defn say [a1 a2 & more] (println a1) (println a2) (doseq [a more] (print a))) (say \1 \2 \5 \4 \3) ;输出 1 2 5 4 3 ; 定义带docstrings的函数 (defn 函数名 "docstrings" [参数1 参数2 & 不定数参数列表] 函数体) ; 示例2 (defn say "输出一堆参数:D" [a1 a2 & more] (println a1) (println a2) (doseq [a more] (print a)))
什么是docstrings呢? docstrings就是Document String,用于描述函数、宏功能。
; 查看绑定或函数的docstrings (cljs.repl/doc name) ; 示例 (cljs.repl/doc say) ;;输入以下内容 ;; ------------------- ;; cljs.user/say ;; ([a1 a2 & more]) ;; 输出一堆参数:D ;;=> nil
; 根据字符串类型的关键字,在已加载的命名空间中模糊搜索名称或docstrings匹配的绑定或函数的docstrings (cljs.repl/find-doc "keyword") ; 示例 (cljs.repl/find-doc "一堆") ;;输入以下内容 ;; ------------------- ;; cljs.user/say ;; ([a1 a2 & more]) ;; 输出一堆参数:D ;;=> nil
题外话!
; 输出已加载的命名空间下的函数的源码 ; 注意:name必须是classpath下.cljs文件中定义的symbol (cljs.repl/source name) ; 示例 (cljs.repl/source say) ;;输入以下内容 ;; ------------------- ;; (defn say ;; "输出一堆参数:D" ;; [a1 a2 & more] ;; (println a1) ;; (println a2) ;; (doseq [a more] ;; (print a)))
; 在已加载的ns中经过字符串或正则模糊查找symbols (cljs.repl/apropos str-or-regex) ; 示例 (cljs.repl/apropos "sa") (cljs.repl/apropos #"sa.a")
; 查看命名空间下的公开的Var (cljs.repl/dir ns) ; 示例 (cljs.repl/dir cljs.repl)
; 打印最近或指定的异常对象调用栈信息,最近的异常对象会保存在*e(一个dynamic var)中 (pst) (pst e)
注意:当咱们使用REPL时,会自动引入(require '[cljs.repl :refer [doc find-doc source apropos pst dir]]
,所以能够直接使用。
因为cljs采用前缀语法,所以咱们熟悉的==
、!=
、&&
和+
等均以(= a b)
、(not= a b)
、(and 1 2)
和(+ 1 2)
等方式调用。
; 值等,含值类型转换,且对于集合、对象而言则会比较全部元素的值 (= a b & more) ; 数字值等 (== a b & more) ; 不等于 (not= a b & more) ; 指针等 (identical? a b) ; 大于、大于等于、小于、小于等于 (> a b) (>= a b) (< a b) (<= a b) ; Surprising!! JS中表示数值范围只能写成 1 < x && x < 10,但cljs中能够直接写成 (< 1 x 10) ; > >= <=均可以这样哦! ; 比较,若a小于b,则返回-1;等于则返回0;大于则返回1 ; 具体实现 ; 1. 若a,b实现了IComparable协议,则采用IComparable协议比较 ; 2. 若a和b为对象,则采用google.array.defaultCompare ; 3. nil用于小于其余入参 (compare a b)
; 或 (or a & next) ; 与 (and a & next) ; 非 (not a)
对于or
和and
的行为是和JS下的||
和&&
一致,
or
返回值为入参中首个不为nil
或false
的参数;而and
则是最后一个不为nil
或false
的参数。Boolean
类型。; 加法,(+)返回0 (+ & more) ; 减法,或取负 (- a & more) ; 乘法, (*)返回1 (*) ; 除法,或取倒数,分母d为0时会返回Infinity (/ a & more) ; 整除,分母d为0时会返回NaN (quot n d) ; 自增 (inc n) ; 自减 (dec n) ; 取余,分母d为0时会返回NaN (rem n d) ; 取模,分母d为0时会返回NaN (mod n d)
取余和取模的区别是:
/** * @description 求模 * @method mod * @public * @param {Number} o - 操做数 * @param {Number} m - 模,取值范围:除零外的数字(整数、小数、正数和负数) * @returns {Number} - 取模结果的符号与模的符号保持一致 */ var mod = (o/*perand*/, m/*odulus*/) => { if (0 == m) throw TypeError('argument modulus must not be zero!') return o - m * Math.floor(o/m) } /** * @description 求余 * @method rem * @public * @param {Number} dividend - 除数 * @param {Number} divisor - 被除数,取值范围:除零外的数字(整数、小数、正数和负数) * @returns {Number} remainder - 余数,符号与除数的符号保持一致 */ var rem = (dividend, divisor) => { if (0 == divisor) throw TypeError('argument divisor must not be zero!') return dividend - divisor * Math.trunc(dividend/divisor) }
至于次方,开方和对数等则要调用JS中Math
所提供的方法了!
; 次方 (js/Math.pow d e) ; 开方 (js/Math.sqrt n)
能够注意到调用JS方法时只需以js/
开头便可,是否是十分方便呢! 根据个人习惯会用**
标示次方,因而自定个方法就好
(defn ** ([d e] (js/Math.pow d e)) ([d e & more] (reduce ** (** d e) more)))
; if (when test then) ;示例 (when (= 1 2) (println "1 = 2")) ; if...else... ; else?的缺省值为nil (if test then else?) ;示例 (if (= 1 2) (println "1 = 2") (println "1 <> 2")) ; if...elseif..elseif...else ; expr-else的缺省值为nil (cond test1 expr1 test2 expr2 :else expr-else) ;示例 (cond (= 1 2) (println "1 = 2") (= 1 3) (println "1 = 3") :else (println "1 <> 2 and 1 <> 3")) ; switch ; e为表达式,而test-constant为字面常量,能够是String、Number、Boolean、Keyword和Symbol甚至是List等集合。e的运算结果若值等test-constant的值(对于集合则深度相等时),那么就以其后对应的result-expr做为case的返回值,若都不匹配则返回default-result-expr的运算值 ; 若没有设置default-result-expr,且匹配失败时会抛出异常 (case expr test-constant1 result-expr test-constant2 result-expr ...... default-result-expr) ;示例 (def a 1) (case a 1 "result1" {:a 2} (println 1)) ; -> 返回 result1,且不执行println 1 ; for (loop [i start-value] expr (when (< i amount) (recur (inc i)))) ; 示例 (loop [i 0] (println i) (when (< i 10) (recur (inc i)))) ; try...catch...finally (try expr* catch-clause* finally-clause?) catch-clause => (catch classname name expr*) finally-clause? => (finally expr*) ; throw,将e-expr运算结果做为异常抛出 (throw e-expr)
cljs最终是运行在JSVM的,因此免不了与JS代码做互调。
; 调用JS函数,如下两种形式是等价的。但注意第二种,第一个参数将做为函数的上下文,和python的方法类似。 ; 最佳实践为第一种方式 (js/Math.pow 2 2) (.pow js/Math 2 2) ; 获取JS对象属性值,如下两种形式是等价的。 ; 但注意第一种采用的是字面量指定属性名,解析时肯定 ; 第二种采用表达式来指定属性名,运行时肯定 ; 两种方式都可访问嵌套属性 (.-body js/document) (aget js/document "body") ; 示例:访问嵌套属性值,若其中某属性值为nil时直接返回nil,而不是报异常 (.. js/window -document -body -firstChild) ;-> 返回body元素的第一个子元素 (aget js/window "document" "body" "firstChild") ;-> 返回body元素的第一个子元素 (.. js/window -document -body -firstChild1) ;-> 返回nil,而不会报异常 (aget js/window "document" "body" "firstChild1") ;-> 返回nil,而不会报异常 ; 有用过Ramda.js的同窗看到这个时第一感受则不就是R.compose(R.view, R.lensPath)的吗^_^ ; 设置JS对象属性值,如下两种形式是等价的。注意点和获取对象属性是一致的 (set! (.-href js/location) "new href") (aset! js/location "href" "new href") ; 删除JS对象属性值 (js-delete js/location href) ; 建立JS对象,如下两种形式是等价的 #js {:a 1} ; -> {a: 1} (js-obj {:a 1}) ; -> {a: 1} ; 建立JS数组,如下两种形式是等价的 #js [1 2] (array 1 2) ; 建立指定长度的空数组 (make-array size) ; 浅复制数组 (aclone arr) ; cljs数据类型转换为JS数据类型 ; Map -> Object (clj->js {:k1 "v1"}) ;-> {k1: "v1"} ; List -> Array (clj->js '(1 2)) ;-> [1, 2] ; Set -> Array (clj->js #{1 2}) ;-> [1, 2] ; Vector -> Array (clj->js [1 2]) ;-> [1, 2] ; Keyword -> String (clj->js :a) ;-> "a" ; Symbol -> String (clj-js 'i-am-symbol) ;-> "i-am-symbol" ; JS数据类型转换为cljs数据类型 ; JS的数组转换为Vector (js->clj (js/Array. 1 2)) ;-> [1 2] ; JS的对象转换为Map (js->clj (clj->js {:a 1})) ;-> {"a" 1} ; JS的对象转换为Map,将键转换为Keyword类型 (js->clj (clj->js {:a 1}) :keywordize-keys true) ;-> {:a 1} ; 实例化JS实例 ; 最佳实践为第一种方式 (js/Array. 1 2) ;-> [1, 2] (new js/Array 1 2) ;-> [1, 2]
简单来讲就是声明式萃取集合元素
; 数组1解构 (defn a [[a _ b]] (println a b)) (a [1 2 3]) ;-> 1 3 ; 数组2解构 (defn b [[a _ b & more]] (println a b (first more))) (a [1 2 3 4 5]) ;-> 1 3 4 ; 数组3解构,经过:as获取完整的数组 (let [[a _ b & more :as orig] [1 2 3 4 5]] (println {:a a, :b b, :more more, :orig orig})) ;-> {:a 1, :b 3, :more [4 5], :orig [1 2 3 4 5]} ; 键值对1解构 ; 经过键解构键值对,若没有匹配则返回nil或默认值(经过:or {绑定 默认值}), (let [{name :name, val :val, prop :prop :or {prop "prop1"}} {:name "name1"}] (println name (nil? val) prop)) ;-> "name1 true prop1" ; 键值对2解构,经过:as获取完整的键值对 (let [{name :name :as all} {:name "name1", :val "val1"}] (println all)) ;-> {:name "name1", :val "val1"} ; 键值对3解构,键类型为Keyword类型 (let [{:keys [name val]} {:name "name1", :val "val1"}] (println name val)) ;-> name1 val1 ; 键值对4解构,键类型为String类型 (let [{:strs [name val]} {"name" "name1", "val" "val1"}] (println name val)) ;-> name1 val1 ; 键值对5解构,键类型为Symbol类型 (let [{:syms [name val]} {'name"name1", 'val "val1"}] (println name val)) ;-> name1 val1 ; 键值和数组组合解构 (let [{[a _ b] :name} {:name [1 2 3]}] (println a b)) ;-> 1 3
是否是已经被Clojure的语法深深地吸引呢?是否是对Special Form,Symbol,Namespace等仍有疑问呢?是否是很想知道如何用在项目中呢?先不要急,后面咱们会一块儿好好深刻玩耍cljs。不过这以前你会不会发如今clojurescript.net上运行示例代码竟然会报错呢?问题真心是在clojurescript.net上,下一篇(cljs/run-at (JSVM. :browser) "搭建恰好可用的开发环境!"),咱们会先搭建一个恰好可用的开发环境再进一步学习cljs。 尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/7040661.html ^_^肥仔John