JavaScript の関数オブジェクトには、「bind」という便利なメソッドがあります。このメソッドは関数内で参照できる this を指定のオブジェクトに束縛できるものです。この関数を使うと、this に関連した良く有る問題をスマートに解決できます。
this にまつわるよくある問題
次の例は、実行後に「こんにちわ ぷんちゃん」とアラートするコードです。
|
var Person = function (name) { this.name = name; } Person.prototype.sayHello = function() { alert('こんにちわ ' + this.name); } var punchan = new Person('プンちゃん'); punchan.sayHello(); //「こんにちわ プンちゃん」と表示される |
では、3秒後に表示させてみましょう。よく次のように書きます。
|
var Person = function (name) { this.name = name; } Person.prototype.sayHello = function() { alert('こんにちわ ' + this.name); } var punchan = new Person('ぷんちゃん'); setTimeout(function() { punchan.sayHello(); //3秒後に「こんにちわ プンちゃん」と表示される }, 3000) |
これは期待通りに動作します。しかし、setTimeout に渡している無名関数は、単に Person.sayHello() をコールしているだけなので、次のようにも書けそうです。
|
setTimeout(punchan.sayHello, 3000); |
しかし、これは期待通りには動作しません。
通常、ブラウザで実行させている時には「こんにちわ undefined」と表示されます。
これは JavaScrip における一般的な動作です。オブジェクトのメソッドはオブジェクトに束縛されているものではなく、その時々のコンテキストにおいて実行されるからです。JavaScript をある程度理解しているプログラマは、このことを良くご存知のはずです。
bind を使って this を束縛する
さて、本題です。
この this を固定してしまえるメソッド「bind」が関数オブジェクトには存在します。
例を見てみます。
|
var Person = function (name) { this.name = name; } Person.prototype.sayHello = function() { alert('こんにちわ ' + this.name); } var punchan = new Person('ぷんちゃん'); setTimeout(punchan.sayHello.bind(punchan), 3000); //3秒後に「こんにちわ プンちゃん」と表示される |
今度は期待通り動作しました。
bind メソッドはこのように、関数内の this を指定のオブジェクトに束縛した新しい関数を返す機能を提供します。上の例では、bind に punchan オブジェクトを与えて、sayHello メソッド内の this が常に punchan オブジェクトとなるような新しい関数を取得し、それを setTimeout に渡すことで、期待通りの動作を実現しました。
このように、bind を使うことで、関数実行時の this を簡単に束縛できます。JavaScript でオブジェクトを利用したスクリプトを書く時にはとても便利ですね!
別のよくありそうな例
もうひとつ、良く有りそうな例を。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
var Speaker = { say: function(wordsGetter) { var words = wordsGetter(); alert(words); } } function Person(nickname) { this.nickname = nickname; } Person.prototype.sayName = function() { Speaker.say(function() { return this.nickname; //この this は Person オブジェクトでは無い! }); }; var person = new Person('Gyu-ri'); person.sayName(); //undefinend |
14行目で this を参照していますが、この時の this は Person オブジェクトでは無くて、グローバルオブジェクト(ブラウザなら通常 window オブジェクト)になります(ちなみにその this が決定されているのは、3行目の呼び出し部です)。
14 行目の this の部分で、Person オブジェクトを参照したい時に、よく次のようにするでしょう。
|
Person.prototype.sayName = function() { var self = this; Speaker.say(function() { return self.nickname; }); }; |
13 行目で期待する this の値を一旦別の変数に移して、その変数を 15 行目で参照しています。これは上手く行きます。しかし、関数オブジェクトの bind を使うと、次のようにも書けます。
|
Person.prototype.sayName = function() { Speaker.say(function() { return this.nickname; }.bind(this)); }; |
15 行目で bind を使って、13〜15 行目に定義した無名関数オブジェクト内での this を指定しています。このケースではこの方がスマートですね!
読みやすさについてはケースバイケースだと思いますが、これは JavaScript でオブジェクトを扱う時の基本テクニックの一つです。このような bind の用法を知っておくことで、より読みやすいコードを書ける場面も増えると思います。
ちなみに、bind にはどんなオブジェクトでも渡すことができます。ここでは this を渡していますが、必要に応じて、様々なオブジェクトを、関数内の this として bind できるということを覚えておいてください。
bind による引数の部分適用
実は bind は this だけではなく関数の引数も束縛することが出来ます。
次の例を見てください。
|
var mul = function(a, b) { alert(a * b); } mul(2, 3); //6とアラートされる //bindで引数を束縛してみる var mul2 = mul.bind(null, 2); mul2(4); //8とアラートされる mul2(16); //32とアラートされる //更にbindで引数を束縛してみる var mul2x5 = mul2.bind(null, 5); mul2x5(); //10とアラートされる |
4 行目の mul(2, 3) は、単純な関数呼び出しです。6とアラートされます。
7 行目で bind を利用して、引数の一つを 2 に束縛した新しい関数 mul2 を得ています。
8、9 行目の意味が分かるでしょうか? 関数 mul2 は、関数 mul の最初の引数が 2 に束縛されているものなので、常に 2 * b の演算を行ってアラートする関数になります。結果、ここではそれぞれ 2 * 4 ⇒ 8、2 * 16 ⇒ 32 というアラートが表示されます(ちなみに、前述の通り bind の第1引数には this とするオブジェクトを指定しますが、ここでは this は使っていないため null としています)。
また、11 行目からのように、引数が部分適用された関数を、更に部分適用する事もできます。
mul.bind(null, 2) で返される関数と等価なコードを書くと、次のようになります。
|
var mul2 = function(b) { mul(2, b); } |
使いどころが難しい機能かも知れませんが、ライブラリなどの汎用性を高める為に利用することが出来そうですし、アイデア次第、使い方次第、ということになると思います。
引数の部分適用の例1
引数の部分適用の例を挙げてみます。
|
post.tags.remove(tag) .done(function() { alert('タグ「' + tag.name + '」を削除しました。'); }) .fail(function() { alert('何か問題が発生して、処理に失敗しました。'); }); |
これは tags というコレクションオブジェクトから任意の tag を削除するコードです。tags.remove メソッドは jQuery.Deferred オブジェクトを返し、tag の削除に成功した時に done、失敗した時に fail にセットされたコールバック関数が呼ばれるというコードです。ここでコールバックでは alert を表示することしかしていません。これを bind を使って書き換えると、次のように書けます。
|
post.tags.remove(tag) .done(alert.bind(window, 'タグ「' + tag.name + '」を削除しました。')) .fail(alert.bind(window, '何か問題が発生して、処理に失敗しました。')); |
如何でしょうか? この例が見やすいかどかは人それぞれだと思いますが、このように使えるということを覚えていると、きっと何かの時に役立つかと思います。
もう一つ、もう少し使いどころがありそうな例を挙げてみます。
引数の部分適用の例2
次のコードは、1秒毎に3回 alert がコールされて「○秒後!」と表示します。もちろん、「1秒後!」「2秒後!」「3秒後!」となることを期待していますが、これは3回とも「4秒後!」と表示されてしまいます。クロージャ変数についてよくある問題ですね。
|
for (var i = 0; i < 3; i++) { setTimeout(function() { alert((i + 1) + '秒後!'); }, 1000 * i); } |
これは例えば次のようにして期待通りの動作をさせることが出来ます。
|
for (var i = 0; i < 3; i++) { setTimeout(function(second) { return function() { alert(second + '秒後!'); }; }(i + 1), 1000 * i); } |
ただ、ちょっとややこしいですね…。
そこで、bind による引数の部分適用を利用すると、次のように書くことができます。
|
for (var i = 0; i < 3; i++) { setTimeout(function(second) { alert(second + '秒後!'); }.bind(null, i + 1), 1000 * i); } |
これはスッキリして、またしっくり来るのではないでしょうか (*’-‘*)
以上です。
ご存知の方はご存知というお話ですが、僕はつい半年位前まで知らなかったもので共有させて頂きました。