[訳]ClojureScriptの中のJavaScriptは互いに操作します。


ClojureScript:JavaScript Interophttp://www.spacjer.com/blog/2014/09/12/clojurescript-javascript-interop/
(原文は15 th of March 2015に更新)
このブログでも言及したように、勉強を続けているClojure(とClojure Script)です。言語をよりよく理解するために、小型のWebアプリケーションを書きました。面白いために、私はすべてのフロントエンドコードをClojureScriptに書き込みます。外部JavaScript API(Bing地図AJAXコントロール)を使う必要があるので、かなり多くのJavaScriptの相互操作コードを書きました。私にとって文法は明らかではありません。これらの情報があるところが見つけられないので、この文章を書きました。これはかなり長いスレッドです。
JavaScriptの例
すべての例をより分かりやすくするために、簡単なJavaScriptコードを定義することができます。
//global variable
globalName = "JavaScript Interop";
globalArray = globalArray = [1, 2, false, ["a", "b", "c"]];
globalObject = {
  a: 1,
  b: 2,
  c: [10, 11, 12],
  d: "some text"
};

//global function
window.hello = function() {
  alert("hello!");
}

//global function
window.helloAgain = function(name) {
  alert(name);
}

//a JS type
MyType = function() {
  this.name = "MyType";
}

MyComplexType = function(name) {
  this.name = name;
}

MyComplexType.prototype.hello = function() {
  alert(this.name);
}

MyComplexType.prototype.helloFrom = function(userName) {
  alert("Hello from " + userName);
}
グローバルスコープ
ClojureScriptは、特別なjs名前空間を定義し、JavaScriptタイプ/関数/方法/グローバルオブジェクト(すなわち、ブラウザwindowオブジェクト)へのアクセスを許可する。
(def text js/globalName)
JS出力:
namespace.text = globalName;
オブジェクトを作成
ClojureScriptでは、構造関数の最後に.を追加することにより、JavaScriptオブジェクトを作成することができます。
(def t1 (js/MyType.))
JS出力:
namespace.t1 = new MyType;
(注:最初はJSコードは括弧が欠けているためだと思いましたが、実際には有効です。構造関数にパラメータがない場合は括弧は省略されます。)
また、オブジェクトを作成する異なる方法で、new関数(JSコンストラクタの名前はドット番号がないはずです)を使用します。
(def my-type (new js/MyComplexType "Bob"))
JS出力:
namespace.my_type = new MyComplexType("Bob");
呼び出し方法
JavaScriptメソッドを呼び出すには、メソッド名の前に.(ポイント番号)を追加する必要があります。
(.hello js/window)
JS出力:
window.hello();
文法飴を抜くと:
(. js/window (hello))
パラメータを私たちの関数に渡す:
(.helloAgain js/window "John")
JS出力:
window.helloAgain("John");
または:
(. js/window (helloAgain "John"))
同じことはオブジェクトを作成することによって達成できます。
(def my-type (js/MyComplexType. "Bob"))
(.hello my-type)
JS出力:
namespace.my_type = new MyComplexType("Bob");
namespace.my_type.hello();
アクセスのプロパティ
ClojureScriptはいくつかの方法JavaScriptの操作属性を提供します。最も簡単なのは、.-属性を使用して文法にアクセスすることである。
(def my-type (js/MyType.))
(def name (.-name my-type))
JS出力:
namespace.my_type = new MyType;
namespace.name = namespace.my_type.name;
同様のことは、パラメータとしてオブジェクトおよび属性の名前(文字列)を受け取るaget関数によって達成され得る。
(def name (aget my-type "name"))
JS出力:
namespace.name = namespace.my_type["name"];
agetはまた、入れ子の属性へのアクセスを許可する。
(aget js/object "prop1" "prop2" "prop3")
JS出力:
object["prop1"]["prop2"]["prop3"];
同じこと(生成されたコードは異なる)は、..文法を使用して達成することができる。
(.. js/object -prop1 -prop2 -prop3)
JS出力:
object.prop1.prop2.prop3;
また、プロパティの値を設定することができます。ClojureScriptは、asetまたはset!の関数を使用できます。aset関数は、属性を文字列の名前として使用する。
(def my-type (js/MyType.))
(aset my-type "name" "Bob")
JS出力:
namespace.my_type["name"] = "Bob";
set!は、属性アクセスを必要とする。
(set! (.-name my-type) "Andy")
JS出力:
namespace.my_type.name = "Andy";
Arayaget関数は、JavaScript配列要素にアクセスするためにも使用され得る。
(aget js/globalArray 1)
JS出力:
globalArray[1];
または、入れ子の要素を取得したい場合は、このような方法で使用できます。
(aget js/globalArray 3 1)
JS出力:
globalArray[3][1];
ネストスコープ
このテーマは私にとってちょっと混乱しています。私のプロジェクトでは、このようなコードを翻訳したいです。
var map = new Microsoft.Maps.Map();
ClojureScriptに行きます。ご覧の通りMap関数はネストされた作用領域にあります。ネスト属性にアクセスするための慣用的な方法は、..またはaget関数を使用することであるが、これはコンストラクタのためには使用できない。このような状況では、私たちはポイント番号を使う必要があります。
(def m2 (js/Microsoft.Maps.Themes.BingTheme.))
またはnew関数を使用します。
(def m1 (new js/Microsoft.Maps.Themes.BingTheme))
この表現を書くと:
(def m3 (new (.. js/Microsoft -Maps -Themes -BingTheme)))
異常があります。
First arg to new must be a symbol at line
                core.clj:4403 clojure.core/ex-info
             analyzer.clj:268 cljs.analyzer/error
             analyzer.clj:265 cljs.analyzer/error
             analyzer.clj:908 cljs.analyzer/eval1316[fn]
             MultiFn.java:241 clojure.lang.MultiFn.invoke
            analyzer.clj:1444 cljs.analyzer/analyze-seq
            analyzer.clj:1532 cljs.analyzer/analyze[fn]
            analyzer.clj:1525 cljs.analyzer/analyze
             analyzer.clj:609 cljs.analyzer/eval1188[fn]
             analyzer.clj:608 cljs.analyzer/eval1188[fn]
             MultiFn.java:241 clojure.lang.MultiFn.invoke
            analyzer.clj:1444 cljs.analyzer/analyze-seq
            analyzer.clj:1532 cljs.analyzer/analyze[fn]
            analyzer.clj:1525 cljs.analyzer/analyze
            analyzer.clj:1520 cljs.analyzer/analyze
             compiler.clj:908 cljs.compiler/compile-file*
            compiler.clj:1022 cljs.compiler/compile-file
JavaScriptオブジェクトを作成
多くの場合、私たちはJavaScriptオブジェクトをClojureScriptから伝達する必要があります。一般的にClojureScriptは自分のデータ構造(可変ではない、持久的なvector、Map、setなど)を処理して純粋なJS対象になります。このようにするいくつかの方法があります。
もしキーパッドの値をリストに簡単なJavaScriptオブジェクトを作成するなら、js-objというマクロを使ってもいいです。
(def my-object (js-obj "a" 1 "b" true "c" nil))
JS出力:
namespace.my_object_4 = (function (){var obj6284 = {"a":(1),"b":true,"c":null};return obj6284;
なお、js-objは、キーおよびベースデータの文字列量(文字列、数字、ブール値)の値として文字列を使用するように強制される。ClojureScriptデータ構造は変わりませんので、このようにします。
(def js-object (js-obj  :a 1 :b [1 2 3] :c #{"d" true :e nil}))
このようなJavaScriptオブジェクトを作成します。
{
  ":c" cljs.core.PersistentHashSet, 
  ":b" cljs.core.PersistentVector, 
  ":a" 1
}
使用する内部タイプが見られます。
cljs.core.PersistentHashSet
cljs.core.PersistentVector
ClojureScriptのキーワードは文字列の前にコロンを付けます。
この問題を解決するために、clj-> js関数を使用できます。「再帰的にClojureScript値をJavaScriptに変換します。Set/Vector/ListはArayとなり、KeywordとSymbolは文字列となり、MapはObjectとなる。
{
  "a": 1,
  "b": [1, 2, 3],
  "c": [null, "d", "e", true]
}
JavaScriptオブジェクトを生産する別の方法もあります。#js reader文法を使用できます。
(def js-object #js {:a 1 :b 2})
生成されたコード:
namespace.core.js_object = {"b": (2), "a": (1)};
#jsを使う時、この文法は内部構造を変えないので、慎重にしてください。
(def js-object #js {:a 1 :b [1 2 3] :c {"d" true :e nil}})
このようなオブジェクトを作成します。
{
  "c": cljs.core.PersistentArrayMap, 
  "b": cljs.core.PersistentVector, 
  "a": 1
}
この問題を解決するには、各ClojureScript構造の前に#jsを追加する必要があります。
(def js-object #js {:a 1 :b #js [1 2 3] :c #js ["d" true :e nil]})
JavaScriptオブジェクト:
{
  "c": {
    "e": null,
    "d": true
  },
  "b": [1, 2, 3 ],
  "a": 1
}
JavaScriptオブジェクトを使う
ある時は、JavaScriptオブジェクトまたはClojureScriptのデータ構造に変換する必要があります。私たちはjs->clj関数を使用してこの点を達成できます。「再帰的にJavaScript配列をClojureScript Vectorに変更し、JavaScriptオブジェクトをClojure Script Mapに変更します。オブジェクトフィールドは、オプション:keywordize-key trueによって変換された文字列のKeywordからなる。
(def my-array (js->clj (.-globalArray js/window)))
(def first-item (get my-array 0)) ;; 1

(def my-obj (js->clj (.-globalObject js/window)))
(def a (get my-obj "a")) ;; 1
関数としての文書で説明されているのは、作成されたMapのキーワード文字列をkeywordに変換する:keywordize-keys trueを使用することができる。
(def my-obj-2 (js->clj (.-globalObject js/window) :keywordize-keys true))
(def a-2 (:a my-obj-2)) ;; 1
その他
JavaScriptを使用した他のすべての方法が失敗した場合、js*はパラメータとして文字列を受信し、そのままJavaScriptコードとして返されます。
(js* "alert('my special JS code')") ;; JS output: alert('my special JS code');
ClojureScript関数を暴露
なお、ClojureScriptからJavaScriptコードを生成する正確な形式はコンパイラの設定に依存する。これらの設定は、Leinininingn project.cljファイルにおいて定義されてもよい。project.cljファイルの関連部分:
:cljsbuild {
    :builds [{:id "dev"
              :source-paths ["src"]
              :compiler {
                :main your-namespace.core
                :output-to "out/your-namespace.js"
                :output-dir "out"
                :optimizations :none
                :cache-analysis true
                :source-map true}}
             {:id "release"
              :source-paths ["src"]
              :compiler {
                :main blog-sc-testing.core
                :output-to "out-adv/your-namespace.min.js"
                :output-dir "out-adv"
                :optimizations :advanced
                :pretty-print false}}]}
上で定義された2つの構成が見られます。devreleaseです。:optimizationsパラメータ--:advanced を使用したコードは圧縮されます(使用されていないコードは削除されます)。
例えば、ClojureScriptコード:
(defn add-numbers [a b]
  (+ a b))
:advancedモードでは、このようなJavaScriptコードにコンパイルされます。
function yg(a,b){return a+b}
関数名は完全に「ランダム」ですので、JavaScriptファイルからは使えません。ClojureScript関数を使用して定義できるように、metadataとしてフラグ:exportを追加する必要があります。
(defn ^:export add-numbers [a b]
  (+ a b))
この:exportキーは、コンパイラに与えられた関数名が外部にエクスポートされることを教えています。これはGoogle Cloure ComplerのexportSymbol関数によって達成されました。しかし、詳細については説明しません。そして、あなたの外部JavaScriptコードから、この関数を呼び出すことができます。
your_namespace.core.add_numbers(1,2);
すべてのダッシュは、下線に変わりますので、ご注意ください。
外部JavaScriptライブラリを使用する:advancedモードは、すべての関数/方法の名前が最小の形に変更されるので、外部ライブラリの呼び出しにも影響を及ぼします。ClojureScriptコードに来ます。ChartオブジェクトからPolarArea関数を呼び出します。
(defn ^:export creat-chart []
  (let [ch (js/Chart.)]
    (. ch (PolarArea []))))
コンパイルが完了したら、コードはこうなります。
function(){return(new Chart).Bc(zc)}
あなたが見ているように、PolarArea方法はBcに変更されました。これは当然、運行エラーを引き起こします。このような状況を防ぐために、どの名前がコンパイラに変更されてはいけないのかを教えてください。これらの名前は外部JavaScriptファイルに定義され(externs.js)、コンパイラに提供されるべきである。私達の例ではexterns.jsファイルはこのように見えるべきです。
var Chart = {};
Chart.PolarArea = function() {};
このファイルについては、コンパイラはproject.clj:externs設定で知らせるべきです。
{:id "release"
              :source-paths ["src"]
              :compiler {
                         :main blog-sc-testing.core
                         :output-to "out-adv/your-namespace.min.js"
                         :output-dir "out-adv"
                         :optimizations :advanced
                         :externs ["externs.js"]
                         :pretty-print false}}
これらのすべてを行う場合、JavaScriptコードを作成するには、PolarArea関数の正しい呼び出しが含まれます。
function(){return(new Chart).PolarArea(Ec)}
ClojureScriptについては、外部JavaScriptライブラリを使用して、より詳細な情報を得るために、Luke VanderHartの優れた文章を読むことをお勧めします。
いつものようにコメントを賞賛します。