(ns my.namespace
(:require-macros [my.macros :as my]))
ClojureScript 的基本原理与 Clojure 非常相似,只是将 JavaScript 作为平台,并额外强调了 JS 的覆盖范围,因为它显然不像 Clojure 那样是一个丰富的平台。
可以在本网站的其他地方找到关于 ClojureScript 基本原理的更深入讨论。
与 Clojure 一样,ClojureScript 支持 REPL 驱动的开发,为各种 JavaScript 环境提供易于启动的 REPL。有关详细信息,请参见 快速入门。
此外,ClojureScript 的自托管功能支持将动态特性扩展到纯 JavaScript 环境,在这些环境中可以创建第三方 REPL 和其他动态设施。
即使在单线程环境中,Clojure 的值、状态、标识和时间模型也很有价值。
原子与 Clojure 中的原子一样
没有引用或 STM
binding
的用户体验类似于 Clojure 中的体验
变量
在运行时不进行具体化
通过分析器访问 Clojure 数据结构,可以避免在开发时对具体化的许多使用
def
生成普通的 JS 变量
目前未实现代理
ClojureScript 托管在 JavaScript VM 上
可以选择使用 Google 的 Closure 编译器进行优化
它 旨在利用 Google 的 Closure 库,并参与其依赖项/要求/提供机制
请参见 快速入门
数字
ClojureScript 目前仅支持映射到 JavaScript 原语的整数和浮点字面量
目前不支持 Ratio、BigDecimal 和 BigInteger 字面量
数字的相等性与 JavaScript 相似,而不是 Clojure:(= 0.0 0) ⇒ true
字符
ClojureScript 没有字符字面量。相反,字符与 JavaScript 中的字符相同(即单字符字符串)
列表、向量、映射和集合字面量与 Clojure 中的相同
宏字符
由于 ClojureScript 中没有字符类型,因此 \
生成一个单字符字符串。
read
read
和 read-string
函数位于 cljs.reader
命名空间中
标记字面量
与 Clojure 标记字面量 相同,只是在编译阶段使用的读取器函数类似于 Clojurescript 宏,因为它们应该返回 Clojurescript 代码形式(或字符串或数字之类的字面量)。
Clojure 编译器不会自动要求 data_readers.clj/c 中引用的读取器函数,但 Clojurescript 编译器会。
有关更多详细信息,请参见 读取器
请参见 快速入门,了解 ClojureScript REPL 的使用方法。
标准 ClojureScript REPL 支持 Clojure 的 main 模式。
ClojureScript 与 Clojure 具有相同的评估规则
load
存在,但仅作为 REPL 特殊函数
load-file
存在,但仅作为 REPL 特殊函数
虽然 Clojure 会清除局部变量,但 ClojureScript 不会
以下 ClojureScript 特殊形式与其 Clojure 对应形式相同:if
、do
、let
、letfn
、quote
、loop
、recur
、throw
和 try
。
var
注释
变量在运行时不进行具体化。当编译器遇到 var
特殊形式时,它会发出一个 Var
实例,该实例反映了 **编译时** 元数据。(这满足了许多常见的 **静态** 使用场景。)
def
注释
def
生成普通的 JS 变量
编译器不会强制执行 :private
元数据
私有变量访问会触发分析警告
:const
元数据
会导致对编译时静态 EDN 值进行内联
导致 case
测试常量(这些常量是解析为 ^:const
变量的符号)与其值一起进行内联
def
形式会评估为 init 形式的值(而不是变量),除非设置了 :def-emits-var
编译器选项(默认为 REPL 的 true
)
if
注释
有关 Java 布尔值的包装类的部分与 ClojureScript 无关
fn
注释
目前在调用 fn 时没有运行时强制执行的元数
monitor-enter
、monitor-exit
和 locking
未实现
ClojureScript 的宏必须在与使用它们的阶段不同的 **编译阶段** 中定义。实现这一点的一种方法是在一个命名空间中定义它们,然后在另一个命名空间中使用它们。
宏通过命名空间声明中的 :require-macros
关键字引用
(ns my.namespace
(:require-macros [my.macros :as my]))
可以采用带糖和其他 ns
变体来代替使用 :require-macros
原语;有关详细信息,请参见下面的命名空间部分。
宏在 *.clj
或 *.cljc
文件中编写,并且在使用常规 ClojureScript 时作为 Clojure 编译,或者在使用自举/自托管 ClojureScript 时作为 ClojureScript 编译。需要注意的是,基于 Clojure 的 ClojureScript 宏生成的代码必须针对 ClojureScript 中的功能。
ClojureScript 命名空间 *可以* 从同一个命名空间中要求宏,只要它们在不同的编译阶段。因此,例如, |
与 Clojure 不同,在 ClojureScript 中,宏和函数可以具有相同的名称(例如,cljs.core/+
宏和 cljs.core/+
函数可以共存)。
你可能想知道:“如果是这样的话,我该怎么得到它呢?” ClojureScript(与 Clojure 不同)有两个不同的阶段,使用两个独立的、不交互的命名空间。宏扩展首先发生,因此像 |
打印
*out*
和 *err*
目前未实现
正则表达式支持
ClojureScript 的 正则表达式支持是 JavaScript 的
断言
在 JVM ClojureScript 中,无法在运行时将 *assert*
动态设置为 false。相反,必须使用 :elide-asserts
编译器选项来实现省略。(另一方面,在自托管 ClojureScript 中,*assert*
的行为与 Clojure 相同。)
nil
虽然在 Clojure 中,nil
与 Java 的 null
相同,但在 ClojureScript 中,nil
等效于 JavaScript 的 null
和 undefined
。
数字
目前,ClojureScript 数字只是 JavaScript 数字
未实现强制转换,因为目前没有要强制转换的类型
字符
JavaScript 没有字符类型。Clojure 字符在内部表示为单字符字符串
关键字
ClojureScript 关键字不保证是 identical?
,要进行快速相等性测试,请使用 keyword-identical?
集合
可用的持久集合
Clojure 实现的移植
持久向量、哈希映射和哈希集的瞬态支持
大多数但不是全部集合函数都已实现
结构化映射
ClojureScript 没有实现 defstruct
、create-struct
、struct-map
、struct
或 accessor
。
defprotocol
和 deftype
、extend-type
、extend-protocol
的工作原理与 Clojure 中的相同
协议不像 Clojure 那样进行具体化,没有运行时协议对象
一些反射功能(satisfies?
)与 Clojure 中的相同
satisfies?
是一个宏,必须传递协议名称
extend
目前未实现
specify
,将不可变值扩展到协议 - 实例级别的 extend-type
,无需包装器
ClojureScript 中的命名空间被编译为 Google Closure 命名空间,这些命名空间表示为嵌套的 JavaScript 对象。重要的是,这意味着命名空间和变量有发生冲突的可能性 - 但是编译器可以检测到这些问题,并且在发生这种情况时会发出警告。
目前,你必须仅使用 ns
形式,并注意以下事项
你必须使用 :use
的 :only
形式
:require
支持 :as
、:refer
和 :rename
不支持 :refer :all
所有选项都可以跳过
在这种情况下,可以将符号直接用作库规范
也就是说,(:require lib.foo)
和 (:require [lib.foo])
都受支持,并且含义相同
:rename
指定了一个映射,用于将引用的变量名称映射到不同的符号(可用于避免冲突)
前缀列表 不受支持
:refer-clojure
的唯一选项是 :exclude
和 :rename
:import
仅可用于导入 Google Closure 类
ClojureScript 类型和记录应使用 :use
或 :require :refer
引入,而不是使用 :import
宏必须在与使用它们的编译阶段不同的 编译阶段 中定义。一种实现方法是在一个命名空间中定义它们,并在另一个命名空间中使用它们。它们通过 ns
的 :require-macros
/ :use-macros
选项引用
:require-macros
和 :use-macros
支持与 :require
和 :use
相同的形式
隐式宏加载:如果需要或使用一个命名空间,并且该命名空间本身需要或使用来自其自身命名空间的宏,那么宏将使用相同的规范隐式地需要或使用。此外,在这种情况下,宏变量可以包含在 :refer
或 :only
规范中。这通常会导致简化的库使用,这样使用命名空间就不必担心显式区分某些变量是函数还是宏。例如
(ns testme.core (:require [cljs.test :as test :refer [test-var deftest]]))
将导致 test/is
正确解析,以及 test-var
函数和 deftest 宏在没有限定的情况下可用。
内联宏规范:为了方便起见,:require
可以提供 :include-macros true
或 :refer-macros [syms…]
。两者都反糖成显式加载包含宏的匹配 Clojure 文件的形式。(这独立于所需要的命名空间是否内部需要或使用自己的宏。)例如
(ns testme.core
(:require [foo.core :as foo :refer [foo-fn] :include-macros true]
[woz.core :as woz :refer [woz-fn] :refer-macros [apple jax]]))
是
(ns testme.core
(:require [foo.core :as foo :refer [foo-fn]]
[woz.core :as woz :refer [woz-fn]])
(:require-macros [foo.core :as foo]
[woz.core :as woz :refer [apple jax]]))
的糖
(ns testme.core (:require [clojure.test]))
自动别名 clojure 命名空间:如果需要或使用不存在的 clojure.*
命名空间,并且存在匹配的 cljs.*
命名空间,那么将加载 cljs.*
命名空间,并且将自动从 clojure.*
命名空间到 cljs.*
命名空间建立别名。例如
(ns testme.core (:require [cljs.test :as clojure.test]))
cljs.js/*load-fn*
功能在自托管/引导的 ClojureScript 中使用。def
和 binding
的工作方式与 Clojure 相同
但在普通 js 变量上
Clojure 可以表示未绑定的变量。在 ClojureScript 中,(def x)
会导致 (nil? x)
为 true
。(在这种情况下,(identical? nil x)
为 false
,但 (identical? js/undefined x)
为 true
。)
原子与 Clojure 中的原子一样
在 Clojure 中,def
会产生 变量本身。在 ClojureScript 中,def
会产生 值,除非设置了 REPL 选项 :def-emits-var(对于 REPL,默认值为 true
)。
目前未实现 Ref 和 Agent
验证器的工作方式与 Clojure 相同
goog/LOCALE
=> "en"
(let [sb (goog.string.StringBuffer. "hello, ")]
(.append sb "world")
(.toString sb))
=> "hello, world"
宿主语言互操作功能(new
、/
、.
等)的工作方式与 Clojure 相同(如果可能),例如
在 ClojureScript 中,Foo/bar
始终表示 Foo
是一个命名空间。它不能用于 Clojure 中常见的 Java 静态字段访问模式,因为 JavaScript 中没有反射信息来确定这一点。
js/Infinity
=> Infinity
特殊命名空间 js
提供对全局属性的访问
(.-NEGATIVE_INFINITY js/Number)
=> -Infinity
^boolean
类型提示:它用于避免检查 if
评估(它处理了这样一个事实,即例如,0
和 ""
在 JavaScript 中为假,在 ClojureScript 中为真)。gen-class
、gen-interface
等在 ClojureScript 中不必要且未实现