今回は久しぶりにActionScript(ECMAScript)関連を。
ECMAScript 5では Function.prototype に bind() メソッドが追加されます。
(JavaScript 1.8.5 で実装、ChromeやFirefox4などでは既に利用可能)
Function.apply() や Function.call() と同じように利用できるので、
また一つ強力な技が備わったという感じでしょうか。
有名なJavascriptライブラリ prototype.js 由来の機能なので、
普段からJSを書いている人にはお馴染みかと思います。
ASでも海外のコミュニティではたまに見かける話題ですね。
ここで Function.bind() を扱う前に Function.apply()/call() のおさらい。
Function.apply() はパラメータ配列で渡されたメソッドが呼ばれるのに対して、
Function.call() はひとつひとつ別のパラメータで渡されたメソッドが呼ばれます。
これらのメソッドを使えば、既存のオブジェクトから機能を借りることができます。
Function – ActionScript 3.0 言語およびコンポーネントリファレンス
* Function.apply()/call() 使用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var one:Object = { name: "object", say: function(greet:String, no:int):String { // 2つ目の引数は後の説明で使います return greet + ", " + this.name + ":" + no; } }; var two:Object = { name: "anothor object" }; // 確認 trace(one.say.("hi", 1)); // => hi, object:1 trace(one.say.apply(two, ["hi", 2])); // => hi, another object:2 trace(one.say.call(two, "hi", 3)); // => hi, another object:3 |
one.say()内のthisはtwoオブジェクトを指しています。
つまり、twoオブジェクトはoneオブジェクトのメソッドだけを借りていることになります。
親子関係を作らず機能だけを一時的に借りるので、税金を払わずに済むのがメリットです。
Function.bind()
Function.apply()/call() に使って他のオブジェクトのメソッドを借りるとき、
借りたメソッド内部のthisが指すオブジェクトは、呼び出しの式をもとに決定されます。
Function.bind() は、thisを特定のオブジェクトに束縛する手段を提供します。
ここで、次のコードではどのような結果が返されるか。。
1 2 |
var say:Function = one.say; // one.say()メソッドについては前述 trace(say("hi", 4)); // => hi, undefined:4 |
この場合、one.say()内部のthisはone自身を指さないので、
nameというプロパティはundefinedになってしまいます。
(this の説明は長くなるのでここでは省略)
Function.bind() を使うとこの問題を解決してくれます。
AS3には実装されていないので、Functionを拡張して作ります。
* Function.bind()
(完全な実装ではなく、あくまで簡易版です)
1 2 3 4 5 6 7 8 9 |
if(!Function.prototype.bind) { Function.prototype.bind = function(thisArg:Object):Function { var fn:Object = this, args:Array = arguments.slice(1); return function():* { return fn.apply(thisArg, args.concat(arguments.slice())); }; }; } |
JSの場合は Array.prototype.slice() を借りてくる方法を使いますが、
AS3だと arguments.slice() を直接呼ぶことができます。
なので、AS3の arguments は配列(Array)ということになります。
公式ドキュメントは arguments.callee/lengthの説明のみで、
サンプルコードも正直ひどいので気付かない人が多いんじゃないでしょうか。
あとたぶん各IDEのコード補間にも出てこないです。
arguments – ActionScript 3.0 言語およびコンポーネントリファレンス
話は少しそれましたが、Function.bind() の使い方。
* 使い方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// one, two オブジェクトは前述のものと同じです var one:Object = { name: "object", say: function(greet:String, no:int):String { return greet + ", " + this.name + ":" + no; } }; var two:Object = { name: "anothor object" }; // twoオブジェクトをthisに束縛 (bind) var twosay:Function = one.say.bind(two); trace(twosay("Bonjour", 5)); // => Bonjour, anothor object:5 // 引数束縛 (引数1つ) var twosay:Function = one.say.bind(two, "Bonjour"); trace(twosay(6)); // => Bonjour, anothor object:6 // 引数束縛 (全ての引数) var twosay:Function = one.say.bind(two, "Bonjour", 7); trace(twosay()); // => Bonjour, anothor object:7 |
とても強力ですね。
引数を持つ関数をコールバックとして渡すシチュエーションなどにおいて、
この引数束縛のパターンは綺麗に問題を解決してくれます。
(AS3ネイティブではなく自前実装なので、速度面では不安が残りますが;)
ちなみにSpark projectにAS3bindというのもあります。
こちらはC++のboost::bind風のI/Fになっているので、ESのものとは少し違いますね。
ES5がそこまで近づいてきた今、次期AS(AS4.0?)はいったいどうなるんでしょう。
ActionScriptはECMAScriptに準拠せずに本当に独自路線で進むつもりなのか、
Flashをご飯の種にしてる方々はその辺をどう考えているのか気になります。
* 参考
JavaScriptパターン ―優れたアプリケーションのための作法
Prototype v1.7 API documentation | Function#bind
bind – MDC Doc Center
MethodClosureに適用できないのが残念ですよね。。。
> kacchan6 さん
実際、Function ClosureでのFunction#bindも今のAS3文化だと使われないと思ってます。。(wonderflを眺めている限り;)