(cljs/run-at (JSVM. :browser) "名稱空間就這麼簡單")

^_^肥仔John發表於2017-06-30

前言

 一個cljs檔案定義一個名稱空間,通過名稱空間可以有效組織程式碼,這是構建大型系統必備的基礎設施。本篇我們就深入理解cljs中的名稱空間吧!

好習慣從"頭"開始

每個cljs檔案首行非註釋的內容必定如下

(ns my-project.core)

而當前的cljs檔案路徑為${project_dir}/src/my_project/core.cljs,很明顯名稱空間與原始碼檔案路徑是一一對應的,對應規則是-對應_.對應/咯~

引入其他名稱空間

 要使用其他名稱空間下的成員,那麼必須先將其引入到當前名稱空間才可以。但注意的是,預設情況下會自動引入cljs.core這個名稱空間,而且會將其成員注入到當前名稱空間中。因此(ns my-project.core)最後會編譯為等價於以下語句

;; 注意:cljs中並不支援:all這種引入,因此這面語句僅僅用於表達注入所有成員而已
(ns my-project.core
 (:require [cljs.core :all]))

所以我們可以直接呼叫reduce而不是cljs.core/reduce
 我們沒可能只呼叫cljs.core的成員吧,那到底如何引入其他名稱空間呢?下面我們一一道來!

通過:require

1.直接引入

(ns my-project.core
 (:require clojure.data))

;; 使用時需要指定成員所屬的名稱空間
(clojure.data/diff 1 2)

2.注入成員到當前名稱空間

; 將clojure.data/diff和clojure.data/Diff兩個成員注入到當前名稱空間
(ns my-project.core
 (:require [clojure.data :refer [diff Diff]]))

;; 直接使用即可
(diff 1 2)
(defrecord MyRecord [x]
    Diff
    (diff-similar [a b]
        (= (:x a) (:x b))))

3.為名稱空間起別名

(ns my-project.core
 (:require [clojure.data :as data]))

;; 使用時需要指定成員所屬的名稱空間的別名
(data/diff 1 2)

4.重新命名注入的成員

(ns my-project.core
 (:require [clojure.data :refer [diff] :rename {diff difference}]))

;; 使用時僅能使用別名
(difference 1 2)
;; (diff 1 2) 這裡使用原名會報錯

5.引入同名稱空間的marco

;; 引入helper.core下的所有macro
(ns my-project.core
 (:require [helper.core :as h :include-macros true]))

(h/i-am-macro1)
(h/i-am-macro2)
(h/i-am-function)

;; 引入helper.core下指定的macro
(ns my-project.core
 (:require [helper.core :as h :refer-macros [i-am-macro1]]))

(h/i-am-macro1)
;; 可以不用指定marco所屬的名稱空間哦!
(i-am-macro1)
(h/i-am-function)

helper/core.cljs檔案

(ns helper.core)

(defn i-am-function []
  (println "i-am-function"))

helper/core.clj檔案

(ns helper.core)

(defmacro i-am-macro1 []
  '(println "i-am-macro1"))
(defmacro i-am-macro2 []
  '(println "i-am-macro2"))

 由於macro是在編譯期展開為列表,然後在執行時解析列表,而JS作為指令碼語言根本就沒有所有編譯期,因此需要將macro寫在獨立的clj檔案中,然後在cljs編譯為js時展開。所以當我們在同一個名稱空間定義普通成員和macro時,只需命名兩個名稱一樣當副檔名不同的cljs和clj即可。

6.一次引入多個名稱空間

(ns my-project.core
 (:require [clojure.data :as data]
           [cljs.test :refer [is]]
           clojure.string))

通過:use

:use其實相當於:require加上:refer那樣,一般建議用後者代替。

(ns my-project.core
  (:use clojure.data :only [diff Diff]))

(diff 1 2)
(ns my-project.core
  (:use clojure.data :only [diff] :rename {diff difference}))

(difference 1 2)

通過:require-macros引入macro

其實通過:require中引入macro已經間接接觸到:require-macros了,因為它實際上會解析成:require-macros來使用的!
1.為名稱空間起別名

(ns my-project.core
  (:require-macros helper.core :as h))

(h/i-am-macro1)

2.注入macro到當前名稱空間

(ns my-project.core
  (:require-macros helper.core :refer [i-am-macro1]))

(i-am-macro1)

3.注入macro到當前名稱空間,並起別名

(ns my-project.core
  (:require-macros helper.core :refer [i-am-macro1] :rename {i-am-macro1 m1}))

(m1)

通過:use-macros引入macro

:use-macros其實相當於:require-macros加上:refer那樣,一般建議用後者代替。

(ns my-project.core
  (:use-macros helper.core :only [i-am-macro1]))

(i-am-macro1)
(ns my-project.core
  (:use-macros helper.core :only [i-am-macro1] :rename {i-am-macro1 m1}))

(m1)

通過:import引入Google Closure中的型別和列舉類

 注意:import只能用於引入Google Closure中的型別,而其他型別、成員等等全部用:require引入就好了。

(ns my-project.core
  (:import goog.math.Long
           [goog.math Vec2 Vec3]))

(Long. 4 6)
(Vec2. 1 2)
(Vec3. 1 2 3)

通過:refer-clojure重置clojure內建的symbol

 我們知道預設情況下會自動注入cljs.core的成員到當前名稱空間中,因此我們可以直接使用+-等函式。如果此時我們自定義一個名為+的函式,那麼就會讓下次要使用加法函式時則需要寫成cljs.core/+,這樣總感覺不太好。那麼我們可以藉助:refer-clojure來重置這些內建symbol了。

(ns my-project.core
  (:refer-clojure :rename {+ math_add}))

(defn + [& more]
  (apply math_add more))

 另外還可以直接丟棄(不用就不要注入夠環保的啊!)

(ns my-project.core
  (:refer-clojure :exclude [+]))

(+) ;; 報錯了!

驚喜:名稱空間clojure.*將自動轉為cljs.*

 cljs的好處就是可以直接使用與宿主環境無關的clj程式碼,所以我們可以直接引入clojure.stringclojure.data等名稱空間,但有時不免會記錯或新版本提供了更貼地氣(針對特定宿主優化過)的版本,那是不是就要改成cljs的版本呢?放心cljs編譯器會自動幫你搞定!

(ns testme.core (:require [clojure.test]))
;; 會自動轉換為
(ns testme.core (:require [cljs.test :as clojure.test]))

require用在REPL中就好了

 在REPL中我們會使用如requireuserequire-macrosimport等macro來引入名稱空間。請緊記,這些確實僅僅用於REPL中而已。而且當我們修改原始碼後,需要通過(require 名稱空間 :reload)來重置並重新載入這個名稱空間,不帶:reload的話新修改的功能將不會生效哦!
 注意:require後的名稱空間需要以單引號為起始,從而避免將其從symbol解析為var然後取其值。如

(require 'clojure.data)
(require '[clojure.set :as s])

最佳實踐

根據clojure-style-guide描述優先順序別如下:
:require :as > :require :refer
:require > :use
而宣告順序如下:
:refer-clojure>:require>:import

總結

 現在我們可以安心開始書寫第一個自定義名稱空間了,但是不是還是有點不安穩的感覺呢?是不是上面提到Special FormSymbolVar等一頭霧水呢?下一篇(cljs/run-at (JSVM. :browser) "簡單型別可不簡單啊~")
尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohnhuang/p/7096800.html ^_^肥仔John

相關文章