2014年2月11日火曜日

連載:ちょっとディープなXPages 第2-10回~JavaScriptをもっと知ろう

みなさん!XPages開発していますか!?
今回は、JavaScriptの継承として利用される、メソッド拝借と、その際に必要となるクロージャについて説明します。

メソッド拝借

メソッド拝借とは、コンストラクタ関数やオブジェクトを定義するときや、既存オブジェクトの処理のために、他のオブジェクトの関数を使ってしまおう、ということです。
あるオブジェクトのメンバーをまるまる継承する場合には、以前に説明したprototypeミックインを行えばいいのですが、1,2個の関数だけを使いたい、または、一時的に関数を利用したい場合には、すべての継承するのは少々大げさですよね。
Javascriptでの関数は、オブジェクトの依存度が低く、変数のように持ち運べる手軽さが有ります。メソッド拝借ではこれを利用します。

オブジェクト定義によるメソッド拝借

まずは、オブジェクト定義のときに拝借するパターンを見てみましょう。
//貸す側
var a = {
    name : 'A',
    getName : function(keisho){
        return this.name + ' ' + keisho;
    }
};
//借りる側
var b = {
    name : 'B',
    getName : a.getName //メソッド拝借
};
//結果
print(a.getName('さん')); // "A さん"
print(b.getName('さん')); // "B さん"
実装方法は、bオブジェクトで、aの関数を = で渡すだけ。非常に簡単です。
ここで注目すべきは、a.getName 関数内の this です。一見するとこの this は、a オブジェクト内で宣言されているので、this.name は常に'A'を返すように思われます。
しかし、this は実行時に評価され、何のオブジェクトの中で呼ばれたかによって動的に変わります。
この例ではこの this の特性を活かして有効に利用していますが、この特性を理解せずに無差別に this を利用していると、痛い目にあうことが多いので注意しましょう。

コンストラクタ関数でのメソッド拝借

コンストラクタ関数でのメソッド拝借も、オブジェクト定義の場合と同じです。
//貸す側
var a = function(){
    this.name = 'A';
    this.getName = function(keisho){
        return this.name + ' ' + keisho;
    };
};
var _a = new a();
//借りる側
var b = function(){
    this.name = 'B';
    this.getName = _a.getName; //メソッド拝借
};
var _b = new b();
//結果
print(_a.getName('さん')); // "A さん"
print(_b.getName('さん')); // "B さん"
注意すべき点としては、この場合お a はコンストラクタ関数なので、それ自身に getName 関数は持っていません。そのため、それから生成したオブジェクト(_a)から継承する必要があります。
また、この例ではクロージャを意識する場合があります。
//貸す側
var a = function(){
    var name = 'A';
    this.getName = function(keisho){
        return name + ' ' + keisho;
    };
};
var _a = new a();
//借りる側
var b = function(){
    var name = 'B';
    this.getName = _a.getName; //メソッド拝借(できてない)
};
var _b = new b();
//結果
print(_a.getName('さん')); // "A さん"
print(_b.getName('さん')); // "A さん" <--注意!!!
この例では、name をプロパティではなく、内部変数にしています。そうすると、_b から _a.getName を呼び出しても、その関数の内部の name は _a のものを参照します。
name が宣言されるのはコンストラクタ関数で、ここを通るのは new a() した時だけです。一見すると name のスコープはコンストラクタ関数内なので new a() の処理が終わった段階で name は無くなりそうです。
しかし、name が getName 内で使用されているために、コンストラクタ関数が終わった後も name 変数は生き残ります。
これが、Javascriptにおけるクロージャの仕様です。
こういった間違いをなくしたり、a の定義から継承したければ、a の宣言で prototype を利用するとよいでしょう。
//貸す側
var a = function(){
    this.name = 'A';
};
a.prototype = {
    getName : function(keisho){
        return this.name + ' ' + keisho;
    }
}
//借りる側
var b = function(){
    this.name = 'B';
    this.getName = a.prototype.getName; //メソッド拝借
};
var _a = new a();
var _b = new b();
//結果
print(_a.getName('さん')); // "A さん"
print(_b.getName('さん')); // "B さん"
prototypeはメンバを持つオブジェクトであり、これではクロージャの入る余地はありません。また、b の宣言に、a から new したオブジェクトが必要ないので、間違いがありません。

既存オブジェクトでのメソッド拝借

つぎは宣言ではなく、実行時に拝借するパターンを見てみましょう。
//貸す側
var a = {
    name : 'A',
    getName : function(keisho){
        return this.name + ' ' + keisho;
    }
};
//借りる側
var b = {
    name : 'B'
};
//結果
print(a.getName('さん')); // "A さん"
print(a.getName.call(b,'さん')); // "B さん" <--メソッド拝借
bオブジェクトは、getName 関数を持ちません。そこで、call関数を利用します。
call関数は、すべての「関数」が暗黙的にメンバーとして持つ「関数」です。
関数が関数を持つのは違和感があるかもしれません。
Javascriptでは、関数もオブジェクトで、functionで関数を定義すると、内部ではJavascriptのビルドインオブジェクト Function を継承して関数オブジェクトが生成されます。そのFunction が call 関数を持っているため、すべての関数が call 関数を持つわけです。
call関数は、その関数を実行する機能を持ちますが、 第1引数には、その関数を実行するときの this となるオブジェクトを指定ます。第2引数以降はその関数の引数を続けます。
よって上の例では、「 this を bとして、a.getName を実行しなさい」という命令になります。

次回以降は

Javascriptについては、一旦ここで締めます。なんか、JavascriptばっかりでXPagesの話してないので。
色々と考えたんですが、これからは中級者向けというより、これまでのNotes技術者のための解説をしていこうかなと思ってます。
LotusScript vs Javascript とか、NotesフォームとXPagesの違い、と言った観点ですかね。
近頃記事の更新間隔が開いているので、次回はもっと早く書きますスマイル
海老原 賢次(EBIHARA Kenji)
リコーITソリューションズ株式会社(RICOH IT SOLUTIONS CO.,LTD.)
鹿児島ソリューション部(Kagoshima Department)

0 件のコメント:

コメントを投稿