CoffeeScript開発者向けの小さなユーティリティ

CoffeeScriptは、まったく別のはるかに魅力的な側面からJavaScriptを見ることができる、本当に素晴らしい言語です。 昔、私がフロントエンドに従事し始めたばかりだったとき-私は文字通りそれを書くことを強制されました(企業標準)、今では元の言語で書くことができません。

このプリプロセッサの舵取りに費やした時間(2年以上)の間に、JSで見たい「ウィッシュリスト」がかなり蓄積されています(利点は他の言語とのコミュニケーションの経験です)。ただし、CoffeeScriptを使用すると、ほぼ独自のデザインを作成できます。 これらの「ウィッシュリスト」について、記事で伝えたい、猫の下でお願いします。

名前空間


最初に見たいのは名前空間です。 ファイルシステムだけでなく、クラス階層にもきちんと配置されたコードを見たくない開発者はいますか? グーグルはいくつかの解決策を提案しましたが、最もエレガントなのは、スペース名としてオブジェクトリテラルを使用することでした。

namespace MyApplication:Some: class Any #      window.MyApplication.Some.Any namespace global: class Some #      window.Some 

次のようになりnamespace関数を呼び出し、 {MyApplication:{Some: _}}送信し{MyApplication:{Some: _}}

唯一の「しかし」- 同様のソリューションにはいくつかの問題がありました。つまり、クラス名を取得するために筋金入りのレギュラーだけでなく、接続ファイルの厳密なシーケンス(最初はMyApplicationスペース、次にMyApplication.Someなど)が必要でした。 私はそれらの致命的な欠陥を取り除くことを試みました、そして、結果は以下のコードでした:

 window.namespace = -> args = arguments[0] target = global || window loop for subpackage, obj of args target = target[subpackage] or= {} args = obj break unless typeof args is 'object' Class = args target = window if arguments[0].hasOwnProperty 'global' name = if !Class.name? # IE Fix args.toString().match(/^function\s(\w+)\(/)[1] else Class.name proto = target[name] or undefined target[name] = Class if proto? for i of proto target[name][i] = proto[i] 

戦闘プロジェクトですでにテストされたコードは、それほど遅くなることはなく、大きな喜びの山に過ぎません。

クラス/関数をインポートする


もちろん、 usinguseimportなどの演算子の存在を望みますが、悲しいかな-これらはプリプロセッサ自体のレベルでのみ実装できますが、言語レベルでは実装できません。 しかし、CoffeeScriptの場合、インポートをほぼ美しくする言語自体のプロパティがあることがわかります。

 {Any} = MyApplication.Some #  MyApplication.Some.Any   Any {Any: Test} = MyApplication.Some #  MyApplication.Some.Any   Test 

これらの操作は似ていuse 。phpでuseしましょう。

 use MyApplication\Some\Any; use MyApplication\Some\Any as Test; 


もちろん、この動作は文書化されています(ポイント:「Destructuring Assignment」の例No. 3 in off。文書化 )ですが、正直なところ、誰かのコードに同様の構造があることに気付いたときはとても驚きました。 ドキュメントを読んだとき-気づかなかった。

定数


CoffeeScriptで定数を実装するための一般的なオプションがいくつかあります。

オプション1


 class Some @MY_DEFINE = 42 @getVar: -> @MY_DEFINE getVar: -> @contructor.MY_DEFINE #  Some.MY_DEFINE 

この実施形態では、長所と短所の両方があります。
+ Some.MY_DEFINEコンタクトすることで、どこからでも定数(変数であるため理論上)を取得できます。
-使用は必ずしも便利ではありません
-これは変数です(つまり、書き換えることができます) __defineGetter__および同様の構造を使用してゲッターを作成すると、読み取りが複雑になります。

オプション番号2


 class Some MY_DEFINE = 42 @getVar: -> MY_DEFINE getVar: -> MY_DEFINE 

長所と短所:
+教室内は見やすく、読みやすく、非常に快適に使用できます
-使い捨て 現在の(およびネストされた)スコープによってのみ制限され、クラス外ではその値を取得することはできません
-値を変更から保護するためにゲッター/セッターを実装することは不可能です

javascript const MY_DEFINE = 42を逆引用符でconst MY_DEFINE = 42 (文字「is」がある)、またはゲッター/セッターを使用して定数を登録するFunctionプロトタイプに関数を追加するなどのオプションがまだありますが、これらはあまり一般的な手法ではありません。私は何も言わず、自分のバージョンを提供する方が良いでしょう(現実に少し近い):

3番目のオプション


 class Ajax define AJAX_UNSENT: 0 define AJAX_OPENED: 1 define AJAX_HEADERS_RECEIVED: 2 define AJAX_LOADING: 3 define AJAX_READY: 4 request: -> #   if xhr.status is AJAX_READY #  - 

関数自体の実装:

 window.define = (args) -> for name, val of args continue if window[name]? do (name, val) -> unless window.__defineGetter__? # IE Fix return window[name] = val window.__defineGetter__ name, -> val window.__defineSetter__ name, -> throw new Error "Can not redeclare define #{name}. Define already exists." #         window.defined = (name) -> return window.__lookupGetter__(name)? && window[name]? 

次のことが起こります:オブジェクトを渡す関数を呼び出します。 次に、getterをwindow登録しwindow 。これにより、目的の値が返され、setterが定数を上書きする機能をブロックします。
長所:
+クラス内も非常に見やすく読みやすい
+コードのどこでも利用可能
- windowぶら下がっている-グローバルは良い解決策ではありませんでしたが、コードの可読性と使いやすさの向上の観点から、これはそれほど重要ではないと思います。衝突の問題は通常のプレフィックスで解決されます。

プライベート変数


この例は単なるボーナスであり、単なるアイデアです。 私自身はこの実装があまり好きではありませんが、これまでのところ最高の方法を思い付くことができませんでした。

 class Some test = $private 'test' constructor: -> @[test] = 23 console.log(new Some) 

ここで何が起こるか:クラス内で、 test変数をvar宣言します。その変数の値は文字列[private test] ($ private関数によって返されます)になります。 次に、この変数を実際の変数の名前として使用します。 また、名前は不可視文字で始まるため、特に接頭辞がランダムな不可視文字から生成される場合、変数へのアクセスは非常に困難です。

実装:

 window.$private = (name) -> unless defined 'ZERO_WIDTH_SPACE' define ZERO_WIDTH_SPACE: '​' #     ,    return "#{ZERO_WIDTH_SPACE}[private #{name}]" 

その結果:
+実際のプライベート変数
+かなり大きなサイズのクラスでは、多くの助けになります。 公開すべきでない不要なメソッド/プロパティからこのクラスのインターフェースをクリアします
-非常にかさばり、い
-使用するには不便
-接頭辞「$」を追加する必要があります、なぜなら それはキーワードであり、予約されています

クラス名


クラス名を取得したり、配列とオブジェクトを区別したい場合があります。 そのような状況は非常に一般的であり、そのような場合のために私は自分のために小さな機能を隠しました:

 nameof [] # 'Array' nameof {} # 'Object' nameof SomeClass # 'SomeClass' 

実装自体は次のようになります。

 window.nameof = (cls) -> if typeof cls is 'object' cls = cls.constructor else if typeof cls isnt 'function' return typeof cls name = unless cls.name? cls.toString().match(/function\s(\w+)\(/)[1] else cls.name return name 


抽象メソッド


おそらく最もエレガントでシンプルなソリューション:

 class Some abstractMethod: abstract class Any extends Some (new Any).abstractMethod() # Error 'Can not call abstract method' #   class Any2 extends Some abstractMethod: -> console.log 42 (new Any).abstractMethod() # 42 

実装は最も単純で最も明白です:

 window.abstract = -> throw new Error 'Can not call abstract method' 


エピローグ


(私の意見では)読みやすさを改善する方法と、いくつかの小さな関数でコードを使用することの便利さについて、いくつかの興味深い例を挙げました。 それらのいくつかは、小さなヘルパーのように、深刻なコードを整理するのに適しているかもしれませんが、一般に1つの役割を果たします-言語自体にいくつかの言語構成要素を追加します。 それが読者に警告したい理由です。 たぶんこれはすべて美しくて、実際にも便利ですが、砂糖は適度に良いので、そのようなクレイジーな機能を使いすぎてはいけません。

PS残念ながら、CoffeeScriptが欠落しているハブ「JavaScript」について謝罪します。

UPD:IEに一定のロールバックを追加、sin =)

Source: https://habr.com/ru/post/J235205/


All Articles