JavaScript おれおれ Advent Calendar 2011 – 16日目
(※「スルー力」は「するーか」じゃなくて「するーりょく」です。)
.apply()を使って他の関数を実行できる事は昨日示しました。
これを応用して、メソッドが呼ばれた際にそのままスルーして、別のメソッドが呼ばれたかのように振る舞う事ができます。
スルー
例によって配列風のオブジェクトを考えます。これに .join()を実装しましょう。
var obj = {
"0": "a",
"1": "b",
"2": "c",
length: 3,
join: function() { /* ??? */ }
};
console.log(obj.join()); // "a,b,c"にしたい
console.log(obj.join("/")); // "a/b/c"にしたい
はてさて、どういう設計で実装しましょうか。仕様は? 試験は?
いえ、自分で書かずに丸ごと放り投げてしまいましょう。ここで .apply()の出番です。
var obj = {
"0": "a",
"1": "b",
"2": "c",
length: 3,
join: function() { return Array.prototype.join.apply(this, arguments); }
};
console.log(obj.join()); // => "a,b,c"
console.log(obj.join("/")); // => "a/b/c"
これでnative codeで実装されている処理を我が物顔で実行できるようになりました。
この fn.apply(this, arguments)という慣用句(イディオム)は、個人的には結構頻繁に使います。
ちなみに.apply()と似たものに .call()というメソッドもあるのですが、この書き方はできません。
おれおれログ出力
もうひとつ具体例を。
デバッグ用にログを出力する関数です。console.log()が使用可能ならそれで、なければalert()を使って出力します。
function log() {
// console.logが使えればそれで出力
if (window.console && console.log) {
console.log.apply(console, arguments);
}
// consoleが使えなければalert()で出力
else {
// alert()は引数がひとつだけなので、結合してから渡す
var text = Array.prototype.join.apply(arguments, [', ']);
alert(text);
}
}
log("abc", 1 + 99);
console.log()は引数の数は無制限なので、引数に名前を付ける事ができません。そこでargumentsオブジェクトを利用します。
一方alert()の引数はひとつだけなので、全てまとめて結合してやる必要があります。が、argumentsは配列ではなく配列風のオブジェクトですから、 .join()がありません。ここでも .apply()を使って .join()を実行してやる事ができます。(昨日の記事を参照。)
あ、ちなみにこの関数↑だと、使い方によってはalert()のダイアログがぽこぽこ出て来て使い勝手良くないかなーと思います。(logて名前も妙だし。)まあ、よしなに。
追記(19:08):Hokamuraさんから反応頂きました。
@ginpei_jp 反応してみました d.hatena.ne.jp/hokaccha/20111…
— Kazuhito Hokamuraさん (@hokaccha) 12月 16, 2011
これだと出力されたとき(主にchromeのdev toolsのときの話し)の行数がconsole.log.applyのところになっちゃうので個人的にはこっちのほうがいい。
var log = (function() { if (window.console && console.log) { return console.log.bind(console); } else { return function() { var text = Array.prototype.join.apply(arguments, [', ']); alert(text); } } })(); log("abc", 1 + 99);これだとlog()実行したところの行数が出る。
勝手に解説しますと、普通コンソールに出力すると内容だけでなくて、console.log()が実行されたファイル名と行数が表示されるんです。
で、ご指摘の通り、僕のコードだと「console.log()を実行した行」がlog()の中なので、常にそこの行数が表示されてしまうわけです。ああ、せっかくの便利機能が無駄に!
そこでHokamuraさんのコードに注目しますと、log()は直接関数として宣言されず、関数を返す匿名関数の戻り値になっています。最終的にlogは関数になるので、あくまでログ出力が「
log()の中」ではなく「log()を実行した場所」になる、というのが重要な点です。
.bind()については説明が面倒なので省略します。戻り値が関数になるって事だけ理解してください。 _(・ω・`_)⌒)_ ごめんね MDN見てね
- bind – MDN (英語です)
ちなみに .bind()はIE 8、Safari 5.1.2では動きませんでした。IE 9では動くみたいです。
(追記ここまで。)
