前言
?兩年多前知道cljs的存在時十分興奮,但因?yàn)楣ぷ髦懈居貌簧?,國?nèi)也沒有專門的職位于是擱置了對其的探索。而近一兩年來又刮起了函數(shù)式編程的風(fēng)潮,恰逢有幸主理新項(xiàng)目的前端架構(gòu),于是引入Ramda.js來療藉心中壓抑已久的渴望,誰知一發(fā)不可收拾,于是拋棄所有利益的考慮,遵循內(nèi)心,好好追逐cljs一番:D
?cljs就是ClojureScript的縮寫,就是讓Clojure代碼transpile為JavaScript代碼然后運(yùn)行在瀏覽器或其他JSVM上的技術(shù)。由于宿主環(huán)境的不同,因此只能與宿主環(huán)境無關(guān)的Clojure代碼可以在JVM和JSVM間共享,并且cljs也未能完全實(shí)現(xiàn)clj中的所有語言特性,更何況由于JSVM是單線程因此根本就不需要clj中STM等特性呢……
?transpile為JS的函數(shù)式編程那么多(如Elm,PureScript),為什么偏要cljs呢?語法特別吧,有g(shù)eek的感覺吧,隨心就好:)
?本文將快速介紹cljs的語言基礎(chǔ),大家可以直接通過clojurescript.net的Web REPL來練練手!
注釋
?首先介紹一下注釋的寫法,后續(xù)內(nèi)容會用到哦!
; 單行注釋 ;; 函數(shù)單行注釋 ;;; macro或defmulti單行注釋 ;;;; 命名空間單行注釋 (comment " 多行注釋 ") #! shebang相當(dāng)于;單行注釋#_ 注釋緊跟其后的表達(dá)式, 如: [1 #_2 3] 實(shí)際為[1 3],#_(defn test [x] (println x)) 則注釋了成個test函數(shù)
數(shù)據(jù)類型
標(biāo)量類型
; 空值/空集nil; 字符串(String)"String Data Type"; 字符(Char) \a \newline ; 布爾類型(Boolean),nil隱式類型轉(zhuǎn)換為false,0和空字符串等均隱式類型轉(zhuǎn)換為truetruefalse; 長整型(Long)1; 浮點(diǎn)型(Float)1.2; 整型十六進(jìn)制0x0000ff; 指數(shù)表示法1.2e3 ; 鍵(Keyword),以:為首字符,一般用于Map作為key:i-am-a-key; Symbol,標(biāo)識符 i-am-symbol ; Special Form ; 如if, let, do等 (if pred then else?) (let [a 1] expr1 expr2) (do expr*)
集合類型
; 映射(Map),鍵值對間的逗號禁用于提高可讀性,實(shí)質(zhì)上可移除掉 {:k1 1, :k2 2} ; 列表(List) [1 2 3] ; 矢量(Vector)'(1 2 3) ; 或 (list 1 2 3) ; 集合(Set)#{1 2 3}
關(guān)于命名-Symbol的合法字符集
?在任何Lisp方言中Symbol作為標(biāo)識符(Identity),凡是標(biāo)識符均會被限制可使用的字符集范圍。那么合法的symbol需遵守以下規(guī)則:
首字符不能是
[0-9:]
后續(xù)字符可為
[a-zA-Z0-9*+-_!?|:=<>$&]
末尾字符不能是
:
以:
為首字符則解釋為Keyword
命名空間
?cljs中每個symbol無論是函數(shù)還是綁定,都隸屬于某個具體的命名空間之下,因此在每個.cljs
的首行一般為命名空間的聲明。
(ns hello-world.core)
文件與命名空間的關(guān)系是一一對應(yīng)的,上述命名空間對應(yīng)文件路徑為hello_word/core.cljs
、hello_word/core.clj
或hello_word/core.cljc
。.cljs
文件用于存放ClojureScript代碼.clj
文件用于存放Clojure代碼或供JVM編譯器編譯的ClojureScript的Macro代碼.cljc
文件用于存放供CljureScript自舉編譯器編譯的ClojureScript的Macro代碼
引入其他命名空間
?要調(diào)用其他命名空間的成員,必須要先將其引入
;;; 命名空間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導(dǎo)入symbol(ns b.core (:require [a.core :refer [say1 say2]])) (say1) ;-> A1(say2) ;-> A2
綁定和函數(shù)
?cljs中默認(rèn)采用不可變數(shù)據(jù)結(jié)構(gòu),因此沒有變量這個概念,取而代之的是"綁定"。
綁定
; 聲明一個全局綁定(declare x); 定義一個沒有初始化值的全局綁定(def x); 定義一個有初始化值的全局綁定(def x 1)
注意:cljs中的綁定和函數(shù)遵循先聲明后使用的規(guī)則。
; 編譯時報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)
函數(shù)
函數(shù)的一大特點(diǎn)是:一定必然有返回值,并且默認(rèn)以最后一個表達(dá)式的結(jié)果作為函數(shù)的返回值。
; 定義(defn 函數(shù)名 [參數(shù)1 參數(shù)2 & 不定數(shù)參數(shù)列表] 函數(shù)體); 示例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的函數(shù)(defn 函數(shù)名 "docstrings" [參數(shù)1 參數(shù)2 & 不定數(shù)參數(shù)列表] 函數(shù)體); 示例2(defn say "輸出一堆參數(shù):D" [a1 a2 & more] (println a1) (println a2) (doseq [a more] (print a)))
什么是docstrings呢?
docstrings就是Document String,用于描述函數(shù)、宏功能。
; 查看綁定或函數(shù)的docstrings(cljs.repl/doc name); 示例(cljs.repl/doc say);;輸入如下內(nèi)容;; -------------------;; cljs.user/say;; ([a1 a2 & more]);; 輸出一堆參數(shù):D;;=> nil
; 根據(jù)字符串類型的關(guān)鍵字,在已加載的命名空間中模糊搜索名稱或docstrings匹配的綁定或函數(shù)的docstrings(cljs.repl/find-doc "keyword"); 示例(cljs.repl/find-doc "一堆");;輸入如下內(nèi)容;; -------------------;; cljs.user/say;; ([a1 a2 & more]);; 輸出一堆參數(shù):D;;=> nil
題外話!
; 輸出已加載的命名空間下的函數(shù)的源碼 ; 注意:name必須是classpath下.cljs文件中定義的symbol (cljs.repl/source name) ; 示例 (cljs.repl/source say) ;;輸入如下內(nèi)容 ;; ------------------- ;; (defn say;; "輸出一堆參數(shù):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)
; 打印最近或指定的異常對象調(diào)用棧信息,最近的異常對象會保存在*e(一個dynamic var)中(pst) (pst e)
注意:當(dāng)我們使用REPL時,會自動引入(require '[cljs.repl :refer [doc find-doc source apropos pst dir]]
,因此可以直接使用。
關(guān)系、邏輯和算數(shù)運(yùn)算函數(shù)
?由于cljs采用前綴語法,因此我們熟悉的==
、!=
、&&
和+
等均以(= a b)
、(not= a b)
、(and 1 2)
和(+ 1 2)
等方式調(diào)用。
關(guān)系運(yùn)算函數(shù)
; 值等,含值類型轉(zhuǎn)換,且對于集合、對象而言則會比較所有元素的值(= a b & more); 數(shù)字值等(== a b & more); 不等于(not= a b & more); 指針等(identical? a b); 大于、大于等于、小于、小于等于(> a b) (>= a b) (< a b) (<= a b); 比較,若a小于b,則返回-1;等于則返回0;大于則返回1; 具體實(shí)現(xiàn); 1. 若a,b實(shí)現(xiàn)了IComparable協(xié)議,則采用IComparable協(xié)議比較; 2. 若a和b為對象,則采用google.array.defaultCompare; 3. nil用于小于其他入?yún)?compare a b)
邏輯運(yùn)算函數(shù)
; 或(or a & next); 與(and a & next); 非(not a)
?對于or
和and
的行為是和JS下的||
和&&
一致,
非條件上下文時,
or
返回值為入?yún)⒅惺讉€不為nil
或false
的參數(shù);而and
則是最后一個不為nil
或false
的參數(shù)。條件上下文時,返回會隱式轉(zhuǎn)換為
Boolean
類型。
算數(shù)運(yùn)算函數(shù)
; 加法,(+)返回0(+ & more); 減法,或取負(fù)(- a & more); 乘法, (*)返回1(*); 除法,或取倒數(shù),分母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)
取余和取模的區(qū)別是:
/** * @description 求模 * @method mod * @public * @param {Number} o - 操作數(shù) * @param {Number} m - 模,取值范圍:除零外的數(shù)字(整數(shù)、小數(shù)、正數(shù)和負(fù)數(shù)) * @returns {Number} - 取模結(jié)果的符號與模的符號保持一致 */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 - 除數(shù) * @param {Number} divisor - 被除數(shù),取值范圍:除零外的數(shù)字(整數(shù)、小數(shù)、正數(shù)和負(fù)數(shù)) * @returns {Number} remainder - 余數(shù),符號與除數(shù)的符號保持一致 */var rem = (dividend, divisor) => { if (0 == divisor) throw TypeError('argument divisor must not be zero!') return dividend - divisor * Math.trunc(dividend/divisor) }
?至于次方,開方和對數(shù)等則要調(diào)用JS中Math
所提供的方法了!
; 次方(js/Math.pow d e); 開方(js/Math.sqrt n)
可以注意到調(diào)用JS方法時只需以js/
開頭即可,是不是十分方便呢!
根據(jù)我的習(xí)慣會用**
標(biāo)示次方,于是自定個方法就好
(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為表達(dá)式,而test-constant為字面常量,可以是String、Number、Boolean、Keyword和Symbol甚至是List等集合。e的運(yùn)算結(jié)果若值等test-constant的值(對于集合則深度相等時),那么就以其后對應(yīng)的result-expr作為case的返回值,若都不匹配則返回default-result-expr的運(yùn)算值 ; 若沒有設(shè)置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,且不執(zhí)行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運(yùn)算結(jié)果作為異常拋出 (throw e-expr)
進(jìn)階
與JavaScript互操作(Interop)
cljs最終是運(yùn)行在JSVM的,所以免不了與JS代碼作互調(diào)。
; 調(diào)用JS函數(shù),以下兩種形式是等價的。但注意第二種,第一個參數(shù)將作為函數(shù)的上下文,和python的方法相似。 (js/Math.pow 2 2) (.pow js/Math 2 2) ; 獲取JS對象屬性值,以下兩種形式是等價的。 ; 但注意第一種采用的是字面量指定屬性名,解析時確定 ; 第二種采用表達(dá)式來指定屬性名,運(yùn)行時確定 ; 兩種方式均可訪問嵌套屬性 (.-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的同學(xué)看到這個時第一感覺則不就是R.compose(R.view, R.lensPath)的嗎^_^ ; 設(shè)置JS對象屬性值,以下兩種形式是等價的。注意點(diǎn)和獲取對象屬性是一致的 (set! (.-href js/location) "new href") (aset! js/location "href" "new href") ; 刪除JS對象屬性值 (js-delete js/location href) ; 創(chuàng)建JS對象,以下兩種形式是等價的#js {:a 1} ; -> {a: 1}(js-obj {:a 1}) ; -> {a: 1} ; 創(chuàng)建JS數(shù)組,以下兩種形式是等價的#js [1 2](array 1 2) ; 創(chuàng)建指定長度的空數(shù)組 (make-array size) ; 淺復(fù)制數(shù)組 (aclone arr) ; cljs數(shù)據(jù)類型轉(zhuǎn)換為JS數(shù)據(jù)類型 ; 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數(shù)據(jù)類型轉(zhuǎn)換為cljs數(shù)據(jù)類型 ; JS的數(shù)組轉(zhuǎn)換為Vector(js->clj (js/Array. 1 2)) ;-> [1 2] ; JS的對象轉(zhuǎn)換為Map(js->clj (clj->js {:a 1})) ;-> {"a" 1} ; JS的對象轉(zhuǎn)換為Map,將鍵轉(zhuǎn)換為Keyword類型 (js->clj (clj->js {:a 1}) :keywordize-keys true) ;-> {:a 1} ; 實(shí)例化JS實(shí)例 (js/Array. 1 2) ;-> [1, 2] (new js/Array 1 2) ;-> [1, 2]
解構(gòu)(Destructuring)
?簡單來說就是聲明式萃取集合元素
; 數(shù)組1解構(gòu)(defn a [[a _ b]] (println a b)) (a [1 2 3]) ;-> 1 3; 數(shù)組2解構(gòu)(defn b [[a _ b & more]] (println a b (first more))) (a [1 2 3 4 5]) ;-> 1 3 4; 數(shù)組3解構(gòu),通過:as獲取完整的數(shù)組(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解構(gòu); 通過鍵解構(gòu)鍵值對,若沒有匹配則返回nil或默認(rèn)值(通過:or {綁定 默認(rèn)值}),(let [{name :name, val :val, prop :prop :or {prop "prop1"}} {:name "name1"}] (println name (nil? val) prop)) ;-> "name1 true prop1"; 鍵值對2解構(gòu),通過:as獲取完整的鍵值對(let [{name :name :as all} {:name "name1", :val "val1"}] (println all)) ;-> {:name "name1", :val "val1"}; 鍵值對3解構(gòu),鍵類型為Keyword類型(let [{:keys [name val]} {:name "name1", :val "val1"}] (println name val)) ;-> name1 val1; 鍵值對4解構(gòu),鍵類型為String類型(let [{:strs [name val]} {"name" "name1", "val" "val1"}] (println name val)) ;-> name1 val1; 鍵值對5解構(gòu),鍵類型為Symbol類型(let [{:syms [name val]} {'name"name1", 'val "val1"}] (println name val)) ;-> name1 val1; 鍵值和數(shù)組組合解構(gòu)(let [{[a _ b] :name} {:name [1 2 3]}] (println a b)) ;-> 1 3
總結(jié)
?是不是已經(jīng)被Clojure的語法深深地吸引呢?是不是對Special Form,Symbol,Namespace等仍有疑問呢?是不是很想知道如何用在項(xiàng)目中呢?先不要急,后面我們會一起好好深入玩耍cljs。不過這之前你會不會發(fā)現(xiàn)在clojurescript.net上運(yùn)行示例代碼居然會報錯呢?問題真心是在clojurescript.net上,下一篇(cljs/run-at (JSVM. :browser) "搭建剛好可用的開發(fā)環(huán)境!"),我們會先搭建一個剛好可用的開發(fā)環(huán)境再進(jìn)一步學(xué)習(xí)cljs。
尊重原創(chuàng),轉(zhuǎn)載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/7040661.html ^_^肥仔John
http://www.cnblogs.com/fsjohnhuang/p/7040661.html