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では動くみたいです。
(追記ここまで。)