兩年多前知道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