ClojureScript 是一門編譯到 JavaScript 的 Lisp 方言, 就像 CoffeeScript.
Clojure 是 Lisp 方言, 因此它的語法基於 S-Expression(S 表達式),
"S 表達式"大量使用圓括號好比 (f arg1 arg2)
來控制代碼的嵌套結構,
甚至於像是日常的 a + b + c
在 S 表達式當中也編程 (+ a b c)
.html
這是一種"前綴表達式"的寫法, 它很靈活, 能夠構造出很是靈活的代碼,
好比這樣一段代碼, 能夠完成 10 之內的奇數的平方求和:node
(->> (range 10) (filter odd?) (map (fn [x] (* x x))) (reduce +))
而後你能夠按照 Lumo, 保存上面的代碼到 app.cljs
, 而後運行它:git
npm install -g lumo-cljs lumo app.cljs
Clojure 爲了能更方便, 使用了方括號和花括號做爲特殊的語法.
上面的代碼當中有個 (fn [x] (* x x))
, 其中函數參數就必需要 [x]
寫.github
這段代碼首先運行生成一個長度爲 10 的列表(List):npm
(range 10) ; (0 1 2 3 4 5 6 7 8 9)
而後運行 filter
函數過濾列表, 使用 odd?
來判斷是不是奇數:編程
(filter odd? (list 0 1 2 3 4 5 6 7 8 9)) ; (1 3 5 7 9)
(fn [x] (* x x))
是一個匿名函數, 傳遞給後面的 map
函數運行使用:api
(map (fn [x] (* x x)) (list 1 3 5 7 9)) ; (1 9 25 49 81)
最後運行的是 reduce
函數, 經過 +
這個函數將列表裏全部的數字相加:瀏覽器
(reduce + (list 1 9 25 49 81)) ; 165
這裏能夠看到 list
能夠表示列表的結構, 而 ->>
會管理後面幾段代碼的執行順序.
這裏是 ->>
是經過宏(Macro)來完成的, 宏的語法頗有難度, 這裏先跳過.bash
Clojure 自己是一門 Lisp 方言, 突出了不可變數數據和惰性計算等等函數式編程的功能,
ClojureScript 是 Clojure 編譯到 JavaScript 的版本, 用來開發網頁或者 Node 應用.
跟 JavaScript 相比, Clojure 的設計更加仔細, 並且做爲 Lisp 有着強大的表達能力,
同時, 對於不可變數據的思考也讓 Clojure 對於併發計算和狀態管理有好的改進.
Clojure 做者作過大學老師, 他給人演講有一種充滿智慧的感受, 也是我信任 Clojure 的緣由.數據結構
JavaScript 和 React 當中寫網頁的時候, 須要 JSX 和 immutable-js,
JSX 表示 Virtual DOM 的代碼中間須要特殊處理 if
switch
等邏輯,
在 ClojureScript 當中 if
和 case
自己就是表達式, 不須要額外處理,
至於不可變數據, ClojureScript 默認的數據已是 immutable data 了, 無需額外引入,
因此 ClojureScript 社區有不少人使用 React, 好比能夠用 Reagent 來定義 React 組件:
(defn simple-component [] [:div [:p "I am a component!"] [:p.someclass "I have " [:strong "bold"] [:span {:style {:color "red"}} " and red "] "text."]])
當你熟練 ClojureScript 的時候, 你能夠變得比 JavaScript 更加靈活和自如.
經過高階函數和宏, 能夠構造出很是精簡的代碼來完成一樣的任務.
ClojureScript 是運行在 JavaScript 環境當中的, 好比瀏覽器或者 Node.js ,
Lumo 是一個基於 V8 和 Node.js 的 ClojureScript 運行環境, 能夠用 npm 安裝:
npm install -g lumo-cljs
啓動 Lumo 能夠獲得一個 REPL 環境, 跟 Node.js 的 REPL 很像:
$ lumo Lumo 1.8.0 ClojureScript 1.9.946 Node.js v9.2.0 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Exit: Control+D or :cljs/quit or exit cljs.user=> (range 10) (0 1 2 3 4 5 6 7 8 9) cljs.user=> (filter odd? (list 0 1 2 3 4 5 6 7 8 9)) (1 3 5 7 9) cljs.user=>
另外一個工具是 shadow-cljs, 更適合編譯代碼, 像 Webpack. 而後也用 npm 能夠安裝.
Lumo 適合用來運行 REPL 和代碼片斷, 而 shadow-cljs 適合作項目開發和編譯.
注意對於 shadow-cljs, 你仍是要在安裝 Java 給它後臺調用的.
這篇文章裏默認操做系統是 macOS 或 Linux. 在 Windows 可能要注意其餘問題.
ClojureScript 當中基礎數據類型的跟 JavaScript 類似, 有字符串, 數字, 布爾值,
另外有個 Keyword(關鍵字)類型, 是一種簡化的字符串, 經常使用在"鍵值對"的"鍵"使用.
作元編程時候還會遇到 Symbol(符號)類型, 不過如今還用不到, 不用管它.
對於長一點的, 建議把代碼寫在一個 app.cljs
文件裏:
(println "Hello ClojureScript!")
而後經過 Lumo 執行這個文件:
$ lumo app.cljs Hello ClojureScript!
Lumo 是基於 Node.js 實現的, 因此你能夠再裏面使用 Node.js API.
不過要在 ClojureScript 裏調用, 須要用一些特殊的語法,
好比 JavaScript 對象都須要用 js/console
這種加 js/
前綴的代碼來寫, 而後寫成這樣 :
(.log js/console "a message!") ; console.log("a message!") (.log js/console (js/require "path")) ; console.log(require"("path))
上面的代碼會打印出數據. 要使用構造器或者調用方法須要一些其餘的語法,
(println (new js/Date)) ; #inst "2018-04-15T08:58:44.338-00:00" (println (.now js/Date)) ; 1523782724340
引用 npm 模塊能夠藉助 require
函數, 在 ClojureScript 裏寫成 js/require
:
(def fs (js/require "fs")) (println (.readdirSync fs "./")) ; #js ["app.cljs" "build.cljs" "out" "src"]
另外一種寫法是將引用的模塊寫在 ns
的定義當中, 而後經過 fs/readdirSync
這個寫法調用:
(ns app ; 使用 :as 關鍵字時, "fs" 模塊會被引入, 生成 `fs` 這個命名空間 (:require ["fs" :as fs])) ; 由於 `fs` 是命空間, 因此這個地方用 `fs/` 的寫法了 (println (fs/readdirSync "./")) ; #js [app.cljs build.cljs out src]
對照上面調用 Node.js API 的方法, 讀取文件也是很是容易的:
(ns app (:require ["fs" :as fs])) (println (fs/readFileSync "app.cljs"))
Clojure 當中提供了一些操做字符串的函數, 可是更多函數寫在 clojure.string 這個命名空間之下:
(ns app (:require [clojure.string :as string])) (println (pr-str (str "12" "34"))) ; "1234" (println (pr-str (subs "123455" 2 3))) ; "3" (println (pr-str (string/split "12345" "3"))) ; ["12" "45"]
Clojure 是一門函數式語言, 對於循環的設計有些特別, 須要寫成尾遞歸的形式.
Clojure 須要藉助 recur
這個關鍵字來控制尾遞歸, 好比這個函數打印 0 到 9 的數字:
(defn f1 [x] (if (< x 10) (do (println x) (recur (+ x 1))))) ; `recur` 會再調用 `f1`, 參數就是 `x+1` 了 (f1 0)
上面的尾遞歸能夠用 loop
簡寫, 在 [x 0]
指定 x
的初始值是 0:
(loop [x 0] (if (< x 10) (do (println x) (recur (+ x 1)))))
這裏的 loop
會先設置 n
是 0, 到了 (recur (inc n))
的地方這個 n
會加上 1.
這樣就模擬了一個 while 循環的語法. Clojure 裏要把變化的數據經過參數傳遞.
這是由於函數式編程當中比較排斥可變的數據, 因此用這種方式更嚴格地限制了數據的修改.
你也能夠用 when
做爲只執行一個分支的 if
的簡寫, 那樣就不用 do
包裹多個表達式了:
(loop [x 0] (when (< x 10) (println x) (recur (+ x 1))))
Clojure 裏經常使用的數據結構有:
'(1 2 3 4)
, 從頭部操做, 可是隨機後面的節點會很慢[1 2 3 4]
, 這個就能很快得進行隨機讀寫了, 不過適合從尾部讀寫{:a 1, :b 2}
跟 JavaScript 之類的語言不同是, Clojure 裏的數據是不可變的,
好比 conj
是個往向量的尾部添加數據的函數, 在 a
的基礎上增長數據,
從這個例子你能夠看到 a
在操做以後是不變的, 要從 b
才能拿到改變的數據:
cljs.user=> (def a [1 2 3 4]) #'cljs.user/a cljs.user=> (def b (conj a 6)) #'cljs.user/b cljs.user=> a [1 2 3 4] cljs.user=> b [1 2 3 4 6]
這個就是不可變數據不同的地方了, 這個是函數式編程很須要的一個功能.
更多的操做數據的函數你能夠在 http://cljs.info/cheatsheet/ 找到.
Clojure 還有不少有意思的功能. 後面的文章會再講, 感興趣能夠找咱們問:
另外感謝一些幫我 review 過草稿的同窗, 我後續還會找人添麻煩, 名字我匿了.