
ご存知のように、 CoffeeScriptはJavaScriptとは少し異なる制御構造のセットを提供します 。
言語開発者が文法を可能な限り簡素化し、すべての指示の詳細な説明を行ったという事実にもかかわらず、多かれ少なかれ非標準のサイクルを作ることは大きな挑戦のままです。
この記事では、 CoffeeScriptでループを操作する原理と密接に関連する制御構造について可能な限り詳細に説明しようとします。
すべてのコードには、 JavaScriptの比較例が付属しています 。
For-in命令
最も単純なforループから始めましょう。
for (var i = 0; i < 10; i++) { //... }
CoffeeScriptでは、次のように記述されます。
for i in [0...10]
範囲は、反復回数を決定するために使用されます。
この場合、 0 ... 10の範囲は、ループの10回の繰り返しを実行することを意味します。
しかし、タイプi <= 10の条件を設定する必要がある場合はどうでしょうか?
for i in [0..10]
一見、何も変わっていませんが、よく見ると、範囲内で1つのポイントが少なくなっていることがわかります。
その結果、次のエントリが取得されます。
for (var i = 0; i <= 10; i++) { //... }
範囲の初期値が最後の[10..0]より大きい場合、結果が反転した逆サイクルが得られます。
for (var i = 10; i >= 0; i--) { //.. }
負の範囲値の使用も許容されることに注意してください:
for i in [-10..0]
したがって、配列に負の値を入力できます。
[0..-3] #[0, -1, -2, -3]
nの階乗を計算する関数の例を使用して、実際の状況を考えてみましょう。
JavaScript:
var factorial = function(n) { var result = 1; for (i = 1; i <= n; i++) { result *= i; } return result; }; factorial(5) //120
CoffeeScript:
factorial = (n) -> result = 1 for i in [1..n] result *= i result factorial 5 #120
上記の例からわかるように、 CoffeeScriptコードはJavaScriptよりもコンパクトで読みやすいです。
ただし、このコードは少し簡略化できます。
factorial = (n) -> result = 1 result *= i for i in [1..n] result
この記事では、これは階乗計算の最後の例ではなく、より効果的な方法については少し後で検討します。
[...]
トピックから少し逸脱して、構成の適用に関連する別の興味深い点に言及することを許可します[...](スライス) 。
他の誰かのコードには、次のような構造が見つかることがあります。
'a,b,c'[''...][0]
もちろん、これは次のことを意味します。
'a,b,c'.slice('')[0]; //a
一見したところ、範囲をスライスと区別することは非常に困難です。 主に2つの違いがあります。
最初に、スライスの1つの極端な値をスキップできます
[1...]
ここでは、この表現の翻訳後に得られるものに特に注意を払いたいと思います。
var __slice = Array.prototype.slice; __slice.call(1);
これは、関数の引数のリストを取得するなど、多くの状況で便利です。
fn = -> [arguments...] fn [1..3] #0,1,2,3
CoffeeScriptには、関数引数のリストを取得するためのより安全でエレガントなオプション( splats )があることに注意してください。
fn = (args...) -> args fn [1..3] #0,1,2,3
算術演算と論理演算を使用することもできます。
[1 + 1...]
第二に、スライスの前にオブジェクトが許可されます
[1..10][...2] #1,2
第三に、スライスでの列挙の使用は許可されます
[1,2,3...]
この例では、単純な連結操作を実行します。
[1, 2].concat(Array.prototype.slice.call(3)); //[1,2,3]
より便利な例:
list1 = [1,2,3] list2 = [4,5,6] [list1, list2 ...] #[1,2,3,4,5,6]
リストの理解
CoffeeScriptでオブジェクトを操作するための最も印象的な構文は、 リストの内包表記です。
1からnまでのすべての階乗計算のリストを取得する方法の例:
factorial = (n) -> result = 1 result *= i for i in [1..n] factorial 5 #1,2,6,24,120
次に、より興味深い例を見て、 ロケーションオブジェクトの最初の5つのメンバーをリストします。
(i for i of location)[0...5] # hash, host, hostname, href, pathname
JavaScriptでは、このコードは次のようになります。
var list = function() { var result = []; for (var i in location) { result.push(i); } return result; }().slice(0, 5);
配列の要素(インデックスではない)のリストを表示するには、もう1つのパラメーターを設定する必要があります。
foo = (value for i, value of ['a', 'b', 'c'][0...2]) # [ a, b ]
一方では、リスト式はオブジェクトを操作するための非常に効率的でコンパクトな方法です。 一方、 JavaScriptで翻訳した後にどのコードを受け取るかを明確に理解する必要があります 。
たとえば、 0〜2の要素のリストを表示する上記のコードは、次のようにさらに効果的に書き換えることができます。
foo = (value for value in ['a', 'b', 'c'][0...2])
または:
['a', 'b', 'c'].filter (value, i) -> i < 2
この時点で、メソッド名と開き括弧の間のスペースに特に注意してください。 このギャップが必要です!
スペースをスキップすると、次のようになります。
['a', 'b', 'c'].filter(value, i)(function() { return i < 2; }); //ReferenceError: value is not defined!
今、あなたはおそらく.filter()オプションが最も好まれた理由を知りたいと思うでしょうか?
実際のところ、 for-ofステートメントを使用すると、トランスレーターは必要なループよりも遅いバージョン、つまりfor-inを置き換えます。
ブロードキャスト結果:
var i, value; [ (function() { var _ref, _results; _ref = ['a', 'b', 'c'].slice(0, 2); _results = []; for (i in _ref) { value = _ref[i]; _results.push(value); } return _results; })() ];
それに直面しよう、結果のコードはひどいです。
ここで、 フィルターメソッドを使用して取得したコードを見てみましょう。
['a', 'b', 'c'].filter(function(value, i) { return i < 2; });
ご覧のとおり、完璧で効率的なコードが得られました!
サーバーでCoffeeScriptを使用する場合、心配する必要はありません。そうでない場合は、 IE9-がフィルターメソッドをサポートしていないことを覚えておく必要があります。 したがって、あなた自身がその可用性に注意する必要があります!
その後、演算子
ご存知のように、 式を解釈するために、 CoffeeScriptパーサーはインデント、改行、キャリッジリターンなどを解析します。
以下は、数値を1からnの2の累乗に増やす一般的なサイクルです。
for i in [1...10] i * i
わかりやすくするために、改行とインデントを使用しました。
ただし、実際の状況では、ほとんどの開発者はこの式を1行で記述することを好みます。
for i in [1...10] then i * i
while 、 if / else 、およびswitch / whenステートメントでは、 thenステートメントはパーサーに式を分離するように指示します。
オペレーター
ここまでは、「 単純な 」サイクルのみを考慮してきましたが、特定のステップで値が欠落しているサイクルについて説明します。
2〜10の偶数のみを出力します。
alert i for i in [0..10] by 2 #0,2,4,6,8,10
JavaScriptでは、このコードは次のようになります。
for (var i = 2; i <= 10; i += 2) { alert(i); }
by演算子は、反復ステップを設定できる要素の範囲に適用されます。
配列の数値や要素だけでなく、文字列でも作業できます。
[i for i in 'Hello World' by 3] #H,l,W,l
byおよびthen演算子は一緒に使用できます。
[for i in 'hello world' by 1 then i.toUpperCase()] # H,E,L,L,O, ,W,O,R,L,D
この例は少し難解であり、実際の状況では「 1つ 」のステップを単純化する必要がありますが、それでも、 その後の演算子の共同作業により、非常にコンパクトで効率的なコードを記述できます。
自分のオペレーター
JavaScriptは多くの場合、 .hasOwnProperty()メソッドを使用します。このメソッドは、in演算子とは異なり、オブジェクトプロトタイプチェーンのプロパティをチェックしません。
var object = { foo: 1 }; object.constructor.prototype.bar = 1; console.log('bar' in object); // true console.log(object.hasOwnProperty('bar')); // false
for-inループの本体で.hasOwnProperty()メソッドを使用する例を考えてみましょう。
var object = { foo: 1 }; object.constructor.prototype.toString = function() { return this.foo; }; for (i in object) { if (object.hasOwnProperty(i)) { console.log(i); //foo } }
.toString()メソッドをプロトタイプオブジェクトに追加したにもかかわらず、ループの本体にはリストされません。 直接アクセスできますが:
object.toString() //1
CoffeeScriptは、これらの目的のために独自の特別な演算子を提供します:
object = foo: 1 object.constructor::toString = -> @foo for own i of object console.log i #foo
for-ofステートメントの2番目のキーを使用する必要がある場合は、カンマで区切って指定する必要があり、 独自の演算子を再度追加する必要はありません。
for own key, value of object console.log '#{key}, #{value}' #foo, 1
条件付きif / elseステートメント
ここで、if / elseステートメントとループを共有することに関連する非常に重要なポイントに注目したいと思います。
JavaScriptアプリケーションでは、同様のコードに遭遇する場合があります。
for (var i = 0; i < 10; i++) if (i === 1) break;
この方法を書いている開発者については議論せず、特に非難しません。
CoffeeScriptで式を正しく記述する方法のみが重要です。
最初に頭に浮かぶのは、これを行うことです。
for i in [0..10] if i is 1 break # Parse error on line 1: Unexpected 'TERMINATOR'
しかし、 CoffeeScriptの 字句解析ルールによると、 ifステートメントの前に予期しない端末値が検出され、解析エラーが発生します!
前の資料から、 then演算子を使用して式を1行で実装できることを覚えています。
for i in [0..10] then if i is 1 break #Parse error on line 1: Unexpected 'POST_IF'
ただし、これは役に立ちませんでした。解析エラーが再び表示されます。
それを理解してみましょう...
実際には、 ifステートメントは、 thenステートメントを使用できる他の命令と同じ規則に従います。 つまり、式が正しく解析されるようにするには、式の後にif演算子を再度追加する必要があります。
for i in [0..10] then if i is 1 then break
したがって、次のコードを取得します。
for (i = 0; i <= 10; i++) { if (i === 1) { break; } }
サイクルの前に、Cのパフォーマンスを確認する必要がある場合があります。 条件:
if (foo === true) { for (i = 0; i <= 10; i++) { if (i === 1) { break; } } }
非決定論的なデータ処理とリスト式の使用により、次のようにコードを表現できます。
(if i is 1 then break) for i in [0..10] if foo is on
この場合、解析エラーは発生しなかったが、 then演算子を使用しなかったという事実に注意してください!
When句
byおよびthenステートメントについてはすでに説明しましたが、リストの次のステートメント、つまり条件付きwhenステートメントについて説明します。
そして、前の例の修正から始めましょう。
if foo is on then for i in [0..10] when i is 1 then break
この場合、コードは以前のバージョンよりも少し大きいことが判明しましたが、表現力と意味がはるかに多くなりました。
正の整数nを法として1から10までの数字の順序を推定する方法の別の例を見てみましょう。
alert i for i in [1..10] when i % 2 is 0
JavaScriptコードに変換した後:
for (i = 1; i <= 10; i++) { if (i % 2 === 0) { alert(i); } }
ご覧のとおり、when演算子を使用すると、配列を操作するためのオプションがさらに増えます。
For-of命令
リスト式を見ているときにfor-ofステートメントを使用する例を見てきました。 次に、 for-ofステートメントを詳しく見てみましょう。for -inを使用すると、オブジェクトのプロパティを反復処理できます。
JavaScriptの for-inステートメントと比較して、すぐに比較してみましょう。
var object = { foo: 0, bar: 1 }; for (var i in object) { alert(key + " : " + object[i]); //0 : foo, 1 : bar }
ご覧のとおり、次の構文を使用してオブジェクトプロパティの値を取得しました: object [i] 。
CoffeeScriptでは、すべてがより簡単です。まず、リスト式を使用してオブジェクトの値を取得できます。
value for key, value of {foo: 1, bar: 2}
第二に、より複雑な式の場合、使い慣れた演算子を使用してより広範な表記法を適用できます。
for key, value of {foo: 1, bar: 2} if key is 'foo' and value is 1 then break
JavaScriptでは 、次のように同じ結果を取得できます。
var object = { foo: 1, bar: 2 }; for (key in object) { if (key === 'foo' && object[i] === 1) { break; } }
for-inを効果的に使用する別の例:
(if value is 1 then alert "#{key} : #{value}") for key, value of document #ELEMENT_NODE : 1, #DOCUMENT_POSITION_DISCONNECTED : 1
キー()メソッドがオブジェクトプロパティのリストを取得する最も効果的な方法であることを思い出させてください。
Object.keys obj {foo: 1, bar: 2} # foo, bar
プロパティ値を取得するには、 keys()メソッドをmap()メソッドと組み合わせて使用する必要があります。
object = foo: 1 bar: 2 Object.keys(object).map (key) -> object[key]; # 1, 2
whileステートメント
for-of / inステートメントに加えて、 CoffeeScriptはwhileステートメントも実装します。
for-inステートメントを見たとき、 nの事実を計算するさらに効率的な方法を示すと約束しました。時間がちょうどいいです:
factorial = (n) -> result = 1 while n then result *= n-- result
率直に言って、最もエレガントな階乗解は次のとおりです。
factorial = (n) -> !n and 1 or n * factorial n - 1
ループ命令
無限ループを作成することが唯一の目的であるため、この命令については長い間説明しません。
loop then break if foo is bar
Reltatブロードキャスト:
while (true) { if (foo === bar) { break; } }
指導まで
untilステートメントはwhileインストルメンテーションに似ていますが、1つの例外は否定が式に追加されることです。
これは、たとえば、文字列内の次の文字セットの位置を見つけるのに役立ちます。
expr = /foo/g; alert "#{array[0]} : #{expr.lastIndex}" until (array = expr.exec('foofoo')) is null
Reltatブロードキャスト:
var array, expr; expr = /foo*/g; while ((array = expr.exec('foofoo')) !== null) { alert("" + array[0] + " : " + expr.lastIndex); } //foo : 3, foo : 6
例からわかるように、式の結果がゼロになるまでループが実行されます。
Do-whileステートメント
CoffeeScriptには do-whileステートメントの実装がないことをすぐに言います。 ただし、単純な操作を使用すると、 loopステートメントを使用して部分的な動作を出力することができます 。
loop #... break if foo()
配列メソッド(フィルター、forEach、マップなど)
ご存知のように、 CoffeeScriptでもJavaSctiptと同じメソッドがすべて利用可能です。
メソッドのこのグループ全体を分析することは意味がありません;例としてmap()メソッドを使用した一般的な動作原理のみを考慮します。
3つの要素の配列を作成し、それぞれを正方形にします。
[1..3].map (i) -> i * i
Reltatブロードキャスト:
[1, 2, 3].map(function(i) { return i * i; });
別の例を考えてみましょう:
['foo', 'bar'].map (value, i) -> "#{value} : #{i}" #foo : 0, bar : 1
2番目の引数であるmapメソッドは、呼び出しコンテキストを受け入れます。
var object = new function() { return [0].map(function() { return this }); }; // [Window map]
マップ内でこれを見ることができるように、 Windowを指し、呼び出しのコンテキストを変更するには、明示的にこれを行う必要があります。
var object = new function() { return [0].map(function() { return this; }, this); }; // [Object {}]
CoffeeScriptは、この目的のために特別な= =演算子を使用します。
object = new -> [0].map (i) => @
Reltatブロードキャスト:
var object = new function() { var _this = this; return [0].map(function() { return _this; }, this); };
つまり、これらの配列メソッドを可能な限り使用します。
github'eに投稿したこれらのメソッドのクロスブラウザー実装
CoffeeScriptで マップおよびフィルターメソッドを使用する実際の例は、 github'eの私のプロジェクトの1つでも見ることができます。
指示/短絡を行う
ご存知のように、 JavaScriptはClosuresを積極的に使用しますが、 CoffeeScriptもこの喜びを奪いません。
匿名の自己結合関数を作成するために、 CoffeeScriptはdoステートメントを使用します。このステートメントは任意の数の引数を取ります。
例を考えてみましょう:
array = []; i = 2 while i-- then array[i] = do (i) -> -> i array[0]() #0 array[1]() #1
このコードの本質は、配列を埋めることです。 同時に、配列の要素には基本的な値は含まれませんが、関数はそれぞれ要素のインデックスを返します。
JavaScriptでは、コードは次のようになります。
var array = [], i = 2; while (i--) { array[i] = function(i) { return function() { return i; }; }(i); } array[0]() //0 array[1]() //1
ネストされた命令
ネストされた命令は他の命令と違いはなく、同じ規則に従います。
たとえば、配列を1から3のペアの要素で埋めます。
list = [] for i in [0..2] for j in [1..2] list.push i list # [0,0,1,1,2,2]
ご覧のとおり、複雑なことは何もありません!
おそらく、これを1行で書きたいと思うでしょう。 それでは、エントリを単純化してみましょう。
list = [] for i in [0..2] then for j in [1..2] then list.push i
PS:私は個人的にそのようなレコードを使用しませんが、遅かれ早かれ他の誰かのコードを操作しなければならないので、これを書くことも許されることを知っておくべきです。
しかし、2番目のサイクルの前に何らかの式を追加する必要がある場合はどうでしょうか。
例として、0〜3から要素の3つのペアを導き出します。
list = [] for i in [0..2] list.push i for j in [1..1] list.push i list #[0,0,1,1,2,2]
これは正しいオプションであり、改善すべき点はあまりありません。 2行目の前に明示的な識別が必要なので、すべてを1行で書くこともできません。 しかし、サイクルの本体は短い表記で書くことができます。
list = [] for i in [0..2] list.push i list.push i for j in [1..1] list #[0,1,2,3]
3行目では、録音のプレフィックス形式とポストフィックス形式の両方を使用できます。
jQueryなど
CoffeeScriptの場合 、どのJavaScriptライブラリを使用しても問題ありません。
最も重要なjQuery関数-.ready()から始めましょう
.ready():
$ -> @
ブロードキャスト結果:
$(function() { return this; });
私はあなたのことは知りませんが、そのような記録はほとんどいつも活発な笑いを引き起こします
リストの次のjQueryメソッドは.each()です 。これは、標準の.forEach()メソッドとほぼ同等です。
$ .each:
$.each [1..3], (i) -> i
ブロードキャスト結果:
$.each([1, 2, 3], function(i) { return i; });
ECMASctipt 6
ご存知のように、将来のECMASctipt 6標準では、ジェネレータ、イテレータ、リスト式を実装する予定です。
Firefoxは現在、ほとんどのドラフト標準をサポートしています。
なぜこれをしているのですか?
実際、将来のES6構文は、今日のCoffeeScriptとほぼ完全に互換性がありません。
たとえば、命令のfor ...は必要以上に一般的になりました。
[value for key, value of [1,2,3]]
出力では、次の
var key, value; [ (function() { var _ref, _results; _ref = [1, 2, 3]; _results = []; for (key in _ref) { value = _ref[key]; _results.push(value); } return _results; })() ]; //[1, 2, 3]
将来の標準では、オブジェクトを介した反復の使用が可能になりますが、これははるかに簡単です。
[for i of [1,2,3]]
すごいですね。
式ジェネレータも利用できます:
[i * 2 for each (i in [1, 2, 3])]; //2,4,6
このような記録も可能になります。
[i * 2 for each (i in [1, 2, 3]) if (i % 2 == 0)]; //2
イテレータも利用可能になります:
var object = { a: 1, b: 2 }; var it = Iterator(lang); var pair = it.next(); //[a, 1] pair = it.next(); //[b, 2]
イテレータは、式ジェネレータと組み合わせて使用することもできます。
var it = Iterator([1,2,3]); [i * 2 for (i in it)]; //1, 4, 6
新しい標準のリリースにより、 CoffeScriptからの多くのチップはそのようなものではなくなり、カーネル開発者は砂糖の位置を維持するために多くの作業を行う必要があることは明らかです。 彼らの幸運を祈ります。