Сумісність з батьківською платформою
ClojureScript — це гостьова мова так само, як і її сестра Clojure. Це означає, що обидві мови орієнтовані на інтеграцію з батьківською платформою (JavaScript для ClojureScript та JVM для Clojure).
Типи
ClojureScript прагне використовувати усі існуючі типи з JavaScript, хоча це може здатися несподіваним для мови, що не має нічого спільного з JavaScript. Ось неповний список особливостей, що наслідуються з батьківської платформи та використовуються в ClojureScript:
- Рядки в ClojureScript — це звичайні рядки з JavaScript
- Числа у ClojureScript — ті самі, що і в JavaScript
- Значення
nil
в ClojureScript перетворюється на null у JavaScript - Регулярні вирази в ClojureScript — екземпляри класу RegExp з JavaScript
- ClojureScript не інтерпретована мова; вона завжди компілюється у JavaScript
- В ClojureScript дуже просто використовувати API батьківської платформи, при цьому семантика таких API не змінюється
- Усі структури даних в ClojureScript компілюються в об'єкти в JavaScript, що імплементують ці структури даних
Окрім цього, ClojureScript будує власні абстракції та структури даних, яких немає у батьківській платформі — вектори, мапи, множини та інші, що були розглянуті у попередніх частинах.
Взаємодія з типами даних батьківської платформи
У ClojureScript є невеликий набір спеціальних форм для роботи з типами даних батьківської платформи, наприклад: виклик методу об'єкту, створення екземплярів класів та доступ до властивостей об'єктів.
Доступ до платформи
Доступ до середовища батьківської платформи у ClojureScript здійснюється через простір імен js/
. Ось приклад використання функції parseInt
з JavaScript:
(js/parseInt "222")
;; => 222
Створення екземплярів класу
Створити екземпляр класу у ClojureScript можна двома способами:
За допомогою спеціальної форми new
(new js/RegExp "^foo$")
Або спеціальної форми .
(js/RegExp. "^foo$")
Рекомендованою формою створення екземплярів є друга — саме їй надає перевагу спільнота ClojureScript, хоча нам невідомі суттєві відмінності між цими двома формами.
Виклик методів об'єкту
Виклик методів об'єкту в ClojureScript дещо відрізняється від JavaScript, де ми пишемо так: obj.method()
. В ClojureScript спочатку записується назва методу, а потім назва об'єкту, а не навпаки. Також, на початку назви методу додається спеціальна форма .
Розглянемо як приклад виклик методу .test()
регулярного виразу:
(def re (js/RegExp "^Clojure"))
(.test re "ClojureScript")
;; => true
Методи екземплярів обʼєктів JavaScript також можна викликати. Перший приклад використовує вже знайому нам форму запису, а другий — демонструє скорочену форму:
(.sqrt js/Math 2)
;; => 1.4142135623730951
(js/Math.sqrt 2)
;; => 1.4142135623730951
Доступ до властивостей об'єкту
Синтаксис доступу до властивостей об'єкту дуже схожий на виклик методу, але замість .
використовується .-
. Розглянемо приклад:
(.-multiline re)
;; => false
(.-PI js/Math)
;; => 3.141592653589793
Скорочена форма доступу до властивостей об'єкту
Символи з префіксом js/
можуть містити крапки для позначення доступу до вкладених властивостей. Обидва наступні вирази викликають одну функцію:
(.log js/console "Hello World")
(js/console.log "Hello World")
А ці вирази вирази отримують доступ до однієї властивості обʼєкту:
(.-PI js/Math)
;; => 3.141592653589793
js/Math.PI
;; => 3.141592653589793
Об'єкти JavaScript
ClojureScript пропонує кілька способів створення об'єктів; кожен з них має свою сферу застосування. Функція js-obj
приймає будь-яку кількість пар «ключ-значення» і повертає об'єкт JavaScript:
(js-obj "country" "FR")
;; => #js {:country "FR"}
Це може стати вам у нагоді, коли треба працювати зі сторонніми бібліотеками JavaScript, що приймають звичайні об'єкти. У прикладі вище ви могли побачити, як виглядає об'єкт в ClojureScript. Насправді, це ще одна форма запису.
#js
— це макрос читача, що додається до мап та векторів, які будуть трансформовані у об'єкти та масиви JavaScript:
(def myobj #js {:country "FR"})
Те саме у JavaScript:
var myobj = {country: "FR"};
У попередньому розділі ми згадували про те, що доступ до властивостей обʼєктів можливий за допомогою синтаксису .-
(.-country myobj)
;; => "FR"
Об'єкти JavaScript є змінними. У ClojureScript можна змінювати значення певних властивостей обʼєктів JavaScript за допомогою функції set!
:
(set! (.-country myobj) "KR")
Перетворення
Усі розглянуті форми створення об'єктів мають один недолік: об'єкти не трансформуються рекурсивно. Це означає, що вкладені об'єкти не будуть перетворені у JavaScript. Розглянемо наступний приклад з мапами в ClojureScript та об'єктами в JavaScript:
(def clj-map {:country {:code "FR" :name "France"}})
;; => {:country {:code "FR", :name "France"}}
(:code (:country clj-map)
;; => "FR"
(def js-obj #js {:country {:code "FR" :name "France"}})
;; => #js {:country {:code "FR", :name "France"}
(.-country js-obj)
;; => {:code "FR", :name "France"}
(.-code (.-country js-obj)
;; => nil
Для вирішення цієї проблеми в ClojureScript є функції clj->js
та js->clj
, що перетворюють структури даних з ClojureScript у JavaScript та навпаки. Зверніть увагу на те, що під час конверсії ключове слово :country
перетворюється на рядок.
(clj->js {:foo {:bar "baz"}})
;; => #js {:foo #js {:bar "baz"}}
(js->clj #js {:country {:code "FR" :name "France"}}))
;; => {"country" {:code "FR", :name "France"}}
Для перетворення вектора на масив використовується спеціалізована функція into-array
:
(into-array ["France" "Korea" "Peru"])
;; => #js ["France" "Korea" "Peru"]
Масиви
У попередньому прикладі ми побачили перетворення існуючої колекції з ClojureScript на масив JavaScript. Для створення нових масивів використовують функцію make-array
:
.Створення масиву довжиною 10 елементів:
(def a (make-array 10))
;; => #js [nil nil nil nil nil nil nil nil nil nil]
В ClojureScript масиви сумісні з абстракцією колекції, тому ви можете використовувати будь-які функції для перетворення масиву значень, як зі звичайними колекціями в ClojureScript. Наприклад, функція count
повертає значення довжини будь-якої колекції:
(count a)
;; => 10
Масиви у JavaScript є змінною структурою даних, тому у ClojureScript також можливо записати значення масиву за певним індексом:
(aset a 0 2)
;; => 2
a
;; => #js [2 nil nil nil nil nil nil nil nil nil]
Значення масиву можна прочитати за індексом, як і в JavaScript:
(aget a 0)
;; => 2
Доступ до значень масиву по індексу та доступ до значень в мапі за ключем мають однаковий синтаксис у JavaScript, тому для роботи з об'єктами можна використовувати ті ж функції, що і для масивів::
(def b #js {:hour 16})
;; => #js {:hour 16}
(aget b "hour")
;; => 16
(aset b "minute" 22)
;; => 22
b
;; => #js {:hour 16, :minute 22}