だいたいJavaScript、CoffeeScript両方のコードサンプルを書いてあるけど、横着してそうでないのもチラホラ。
letに相当する何か / スコープを守ろう
まずは簡単なところから。
JavaScriptは関数スコープ
for
やif
等でスコープが作られる文化圏から来た人は注意。
思わぬところで思わぬ変数にアクセスできる。
var sum = 0;
for (var i = 0; i < 10; i++) {
sum += i;
}
console.log(i); // -> 10
// 変数を局所化
var sum = (function () {
var s = 0;
for (var i = 0; i < 10; i++) {
s += i;
}
return s;
}());
console.log(i); // -> undefined
カプセル化
prototype
+ new
でOOPっぽく書きたいニーズもあるようだが、それだと全てのプロパティがpublicになるので (クラスのないJavaScriptで public という言い方をしていいのか悩むところだが) privateを実現するにはclosureを使う。
var obj = (function () {
var invisibleVar = 1;
return {
get: function () {return invisibleVar;},
add: function (num) {invisibleVar += num;}
};
}());
カプセル化 / CoffeeScript
とくに関数リテラルのすっきり感は異常。
obj = do ->
invisibleVar = 1;
{
get: -> invisibleVar
add: (num) -> invisibleVar += num
}
CoffeeScriptにはクラスがあるが、privateなプロパティは作れない。
ファクトリにしてもいい
factory = ->
invisibleVar = 1;
{
get: -> invisibleVar
add: (num) -> invisibleVar += num
}
o1 = factory()
o2 = factory()
JavaScriptのサンプルは割愛。
new (prototype) vs closure
ぶっちゃけnew (prototype) の方が高速でメモリ使用量も少ない。
- new (prototype) のメリット
- 速度とメモリ消費量の点で優れる
-
Object.prototype.isPrototypeOf
を使って型の判定みたいなことができる
- closureのメリット
- カプセル化ができる
- new/thisのトリッキーな挙動から逃れられる
CoffeeScriptのswitch
で使えるprogn
っぽい何か
※このセクションは不要だと分かりました
then
の後に複数の式を書きたい、という場合。
then
を書かなければいいだけ、とコメントでご指摘頂いて判明したのですが、当初は以下のように記述していました。
一応誤情報として残しておきます。
誤
score = 76
grade = switch
when score < 60 then 'F'
when score < 70 then 'D'
when score < 80 then 'C'
when score < 90 then do ->
r = Math.random()
score + r
else 'A'
無名関数でラップしないと構文エラー。
正
ところがこれで済むようです。
score = 76
grade = switch
when score < 60 then 'F'
when score < 70 then 'D'
when score < 80 then 'C'
when score < 90
r = Math.random()
score + r
else 'A'
部分適用
蛇足だけどカリー化とは厳密には異なる。
// JavaScript
var fun1 = function (a, b) {
return a + b;
};
var fun2 = fun1.bind(null, 1);
console.log(fun2(2)); // -> 3
# CoffeeScript
fun1 = (a, b) ->
a + b;
fun2 = fun1.bind null, 1
console.log(fun2 2) # -> 3
オブジェクトAのメソッドを, オブジェクトBが俺のモノだと実行する
call, applyの違いはメソッドへの引数の渡し方
// JavaScript
a.prototype.method.call(b, arg1, arg2);
a.prototype.method.apply(b, [arg1, arg2]);
# CoffeeScript
a::method.call b, arg1, arg2
a::method.apply b, [arg1, arg2]
配列のようで配列でない何かを配列にする
[]
で要素にアクセスできてlength
プロパティを持つものならいけるっぽい
Function.prototype.apply
の応用例としてもどうぞ。
// JavaScript
function f () {
var argArr = Array.prototype.slice.call(arguments);
}
var strArr = Array.prototype.slice.call('hogehoge');
# CoffeeScript
f = ->
argArr = Array::slice.call arguments
strArr = Array::slice.call 'hogehoge'
文字列の配列化なら正直こっちの方が簡潔
strArr = 'hogehoge'.split ''
配列操作のコールバックで忘れがちな引数
forEach
、map
、some
等の第二引数、第三引数は、indexと元の配列である。
reduce
ならば第三、第四引数がこれに該当。
// JavaScript
[1, 1, 2, 3, 5].reduce(function (a, b, index, array) {
return a + b;
}, 'initial value'); // reduce系なら初期値をここでセット
# CoffeeScript
[1, 1, 2, 3, 5].reduce(((a, b, index, array) ->
a + b
), 'initial value')
# CoffeeScriptならリスト内包表記も使えるから合わせ技が便利よね
元の配列の渡し方はどちらがよい?
その1、レキシカルスコープで渡す
array1.map(function (e, i) {
return array1[i + 1] + e;
});
その2、コールバックの引数を使う
array1.map(function (e, i, arr) {
return arr[i + 1] + e;
});
ベンチマークによる速度比較
コールバックの引数を使った方が速い。恐らく変数名の解決のコストがないため。
以下のコードをChromium 30.0で実行したところ、36.4msec vs 218.8msecでコールバックの引数を使った方が高速だった。
function elapse (f) {
var i,
times = 10,
start = (new Date()).getTime();
for (i = 0; i < times; i++) {
f();
}
console.log(((new Date()).getTime() - start) / times + 'msec');
}
var a = [], i, start;
for (var i = 0; i < 1000*1000; i++) {
a.push(i);
}
elapse(function () {
a.forEach(function(e, i, arr) {
var l = arr.length;
});
});
elapse(function () {
a.forEach(function(e, i, arr) {
var l = a.length;
});
});
解説
JavaScriptでは、使用された変数の名前が現在実行している関数のスコープにない場合、
その1つ外側の関数のスコープに同名の変数がないか探しにいき、そこでもなければさらに外側を探し…
と変数の解決を行おうとする (グローバルスコープにもなければundefined
となる) 。
したがって、より近いスコープに変数があるほど効率はよくなる。
なお上記のベンチマークの例だと、配列の変数が1つどころが2つ外側のスコープにあるので、
あまり厳密な測定ではないが、そこは気にしないということで。
関数を否定した関数を返す関数
あんまり使わないかな?
DOMイベントで、何かがOnの時とOffの時にそれぞれコールバックを設定する時に、
Offの時はOnの時の否定を取るような場合に、こういうのを書くとDRYになるかと (日本語が訳わからん) 。
// JavaScript
var fun = function (a, b) {
return a < b;
};
var negate = function (f) {
return function () {
var args = Array.prototype.slice.call(arguments);
return !f.apply(this, args);
};
};
var nFun = negate(fun);
console.log(fun(1, 2)); // -> true
console.log(nFun(1, 2)); // -> false
# CoffeeScript
fun = (a, b) -> a < b
negate = (f) ->
->
args = Array::slice.call(arguments);
!f.apply(this, args);
nFun = negate fun
mix-in
これはまだ試験運用中。
var mixin = function (obj, module) {
var key, property;
for (key in module) {
property = module[key];
if (module.hasOwnProperty(key) && typeof property === 'function') {
obj[key] = function () {
return module[key].apply(obj, Array.prototype.slice(arguments));
};
}
}
return obj;
};
mixin(myObject, myModule);
CoffeeScriptだとclass等というものが使えるらしい
delegateと見せかけた何か
myModule =
f: -> 'OK'
mixin = (clazz, module) ->
moduleName = "__module__#{(new Date()).getTime()}"
clazz.prototype[moduleName] = module
for name, property of module when typeof property is 'function'
# IIFE (後述)
do (_name = name, fn = property) ->
clazz.prototype[_name] = ->
fn.apply(this[moduleName], Array::slice.call(arguments))
class Hoge
constructor: ->
mixin Hoge, myModule
IIFE (即時関数) とはなんぞや
匿名関数を作ってそのまま実行しているものを指す。冒頭でスコープを作るのに使ったアレ。ここではループの変数を固定化するのに用いている。
# 悪い例
myModule =
foo: -> 'FOO'
bar: -> 'BAR'
mixin = (clazz, module) ->
moduleName = "__module__#{(new Date()).getTime()}"
clazz.prototype[moduleName] = module
for name, property of module when typeof property is 'function'
clazz.prototype[name] = ->
property.apply(this[moduleName], Array::slice.call(arguments))
mixin(Hoge, myModule)
console.log Hoge::foo()
関数foo
をコールするとBAR
って返ってきやがります。
これは、JavaScriptの変数が関数スコープなのとClosureの合わせ技なためで、forループで使っているproperty
という変数が、ループの最後にセットされた値を参照することになるため。
なので関数の引数として変数をbindingすることによってこの問題を回避する。
for name, property of module when typeof property is 'function'
do (_name = name, fn = property) ->
clazz.prototype[_name] = ->
fn.apply(this[moduleName], Array::slice.call(arguments))
CoffeeScriptをらしく美しく / do
とwhen
最初、即時関数を素朴にJavaScriptそのまんまで
for name, property of module
if typeof property is 'function'
((_name, fn) ->
clazz.prototype[_name] = ->
fn.apply(this[moduleName], Array::slice.call(arguments))
)(name, property)
とやっていたところ、※欄にてdo
、when
を使ってより簡潔に記述可能と教えて頂き、現在の形に修正しています。
immutableにしたいかね?
var immutable = Object.freeze({
foo: 'FOO',
bar: 'BAR'
});
immutable.foo = 'A'; // 返事がないただの屍
(function () {
// use strictによって例外が発生するようになる
'use strict';
immutable.foo = 'B'; // TypeError
})();