(cljs/run-at (JSVM. :browser) "命名空间就这么简单")

简介: 一个cljs文件定义一个命名空间,通过命名空间可以有效组织代码,这是构建大型系统必备的基础设施。本篇我们就深入理解cljs中的命名空间吧!

前言

 一个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

目录
相关文章
|
SQL 分布式计算 资源调度
线上 hive on spark 作业执行超时问题排查案例分享
线上 hive on spark 作业执行超时问题排查案例分享
|
9月前
|
消息中间件 存储 缓存
招行面试:如何让系统抗住双十一 预约抢购活动?10Wqps级抢购, 做过吗?
本文由40岁老架构师尼恩撰写,针对一线互联网企业如得物、阿里、滴滴等的面试题进行深度解析。文章聚焦于如何设计系统以应对大促活动中的预约抢购场景,涵盖从预告到支付的完整流程。尼恩通过系统化、体系化的梳理,帮助读者提升技术实力,轻松应对高并发挑战,并提供了详细的架构设计和解决方案。文中还分享了《尼恩Java面试宝典》等资源,助力求职者在面试中脱颖而出,实现“offer直提”。更多内容及PDF资料,请关注公众号【技术自由圈】获取。
|
7月前
|
图形学 UED
unity Tab键实现切换输入框功能
该脚本用于简化输入框之间的Tab键切换操作。只需将脚本挂载在InputField上,无需其他设置。脚本通过监听Tab键和Shift键组合,自动选择下一个或上一个可交互的InputField,提升用户体验。
|
11月前
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
【10月更文挑战第8天】在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。
200 1
|
6月前
|
存储 人工智能 云栖大会
【云栖大会】阿里云设计中心 × 教育部协同育人项目成果展,PAI ArtLab助力高校AIGC教育新路径
【云栖大会】阿里云设计中心 × 教育部协同育人项目成果展,PAI ArtLab助力高校AIGC教育新路径
|
9月前
|
机器学习/深度学习 数据采集 搜索推荐
使用Python实现深度学习模型:智能食品消费行为预测
使用Python实现深度学习模型:智能食品消费行为预测
236 8
|
9月前
|
机器学习/深度学习 数据采集 人工智能
智能化运维在企业IT管理中的应用与实践####
本文深入探讨了智能化运维(AIOps)的核心技术原理,通过对比传统运维模式,揭示了AIOps如何利用大数据、机器学习等先进技术提升故障预测准确性、优化资源分配及自动化处理流程。同时,文章详细阐述了智能化运维平台的实施步骤,包括数据收集与分析、模型训练与部署、以及持续监控与优化,旨在为企业IT部门提供一套切实可行的智能化转型路径。最后,通过几个典型应用案例,如某大型电商平台的智能告警系统和金融企业的自动化故障排查流程,直观展示了智能化运维在实际业务场景中的显著成效,强调了其在提升运维效率、降低运营成本方面的关键作用。 ####
226 4
|
10月前
|
存储 NoSQL PHP
如何用Redis高效实现点赞功能?用Set?还是Bitmap?
在众多软件应用中,点赞功能几乎成为标配。本文从实际需求出发,探讨如何利用 Redis 的 `Set` 和 `Bitmap` 数据结构设计高效点赞系统,分析其优缺点,并提供 PHP 实现示例。通过对比两种方案,帮助开发者选择最适合的存储方式。
292 3
|
存储 编解码 数据挖掘
一篇文章掌握大厂成本优化框架
一篇文章掌握大厂成本优化框架