JavaScript中級者への道【2. 4種類のthis】
JavaScriptのつまづきやすそうなところ
- 関数はオブジェクトの一種
- 4種類のthis ← いまここ
- 関数スコープ
- 非同期関数
- コールバック関数
- クロージャ
- プロトタイプ継承
参考記事
JavaScriptの「this」は「4種類」??
調べたらよい記事がありました。基本的な解説はこちらを読んでください。(丸投げ)
とても分かりやすく解説されています。
↑の記事をざっくりまとめると
関数の呼び出しには以下の4つのパターンがあります。
呼び出しのコンテキストによって、関数内のthisの参照先が異なります。
1:メソッド呼び出しパターン
myObject.show(); // thisはmyObjectを指す
2:関数呼び出しパターン
show(); // thisはグローバルオブジェクトを指す
3:コンストラクタ呼び出しパターン
var myObject = new MyObject(0); // thisは新規に返されるオブジェクト(myObject)を指す
4:apply,call呼び出しパターン
myObject.add.apply(yourObject, [2, 10]); // thisは第一引数の任意のオブジェクト(yourObject)を指す
myObject.add.call(yourObject, 2, 10) // thisは第一引数の任意のオブジェクト(yourObject)を指す
関数呼び出し?コンストラクタ?
仮に、以下のようなコードがあるとします。
function foo (name, age) {
this.name = name;
this.age = age;
}
これは、ただの関数でしょうか?それともコンストラクタでしょうか?
中のコードを見るとコンストラクタっぽいな。でも関数名はアッパーキャメルケースじゃないしな。
答えは「呼び出し方によって変わる」です。つまりどちらも正解になります。
// コンストラクタとして使うなら
var matsuby = new foo('matsuby', 24);
// 関数呼び出しとして使うなら
foo('matsuby', 24);
ただし、fooを関数呼び出しとして使った場合、
2:関数呼び出しパターン
show(); // thisはグローバルオブジェクトを指す
に該当するので、グローバルオブジェクトに「name:'matsuby'」「age:24」が追加されます。
これが俗にいう「グローバル汚染」というものの1種です。
これを防ぐ為の手段として、コンストラクタとして使われることを想定している関数は
関数名にアッパーキャメルケースを使おうという「慣例」が生まれています。
なので、先ほど定義した関数fooは下記のように書いておくべきでした。
// これは名前と年齢を持つオブジェクトを返すコンストラクタだよ!
// なので、呼び出す時は絶対に"new"を付けるのを忘れないでね!
function Foo (name, age) {
this.name = name;
this.age = age;
}
「Humanにしろよ」とか「コメント付けなくても分かるよ」とかは一旦置いておいて。
命名規則はあくまで「慣例」なので、別に頭文字を大文字にしただけでふるまいは変わりません。
newをつけずに呼び出した場合は、先ほどの例と変わらずグローバル汚染するわけです。
The Good PartsでCrockfordがコンストラクタをサブセットに含めない理由がこれです。
それとコンストラクタはクラスベースの考え方なので、プロトタイプベースの考え方を見えにくくするんだとか…。
少々話が脱線していますが、コンストラクタを使うべきか使わざるべきかは
意見が分かれるところでもあるので、見ておくと面白いかもしれない記事を紹介しておきます。
・newを封印するべき4つの理由
・new を不当に貶める陰謀と JavaScript におけるクラスの継承構造の話
5:Function.prototype.bind()のthis
JavaScriptの「this」は「4種類」??には無かったので追加で紹介します。
取り敢えずMDNでAPIの仕様について調べてみましょう。
概要
bind() メソッドは、呼び出された時に新しい関数を生成します。最初の引数 thisArg は新しい関数の this キーワードにセットされます。2 個目以降の引数は、新しい関数より前に、ターゲット関数の引数として与えられます。
構文
fun.bind(thisArg[, arg1[, arg2[, ...]]])
【 bind()について、抑えておくべきポイント 】
1. 「新しい関数を生成します」という記載の通り、bind()の戻り値は関数です
2. 第1引数に指定したオブジェクトが戻り値の関数のthisに束縛されます
3. 第2引数以降に指定した変数が戻り値の関数の引数に束縛されます
※今回のテーマはあくまで「this」なので、ポイントの3については一旦忘れましょう
【 bind()を使って実現したいこと・使用するケース 】
・元のオブジェクトへの参照を保ったまま、オブジェクトのメソッドを取り出して実行したり、渡したりしたい
・取り出したいメソッドがthisを使っている
【 失敗例 】
// 名前と、それを表示するメソッドを持つオブジェクトを作成する
var obj = {
name : 'matsuby',
sayName : function () {
console.log(this.name);
}
};
// オブジェクトからメソッドを取り出す
var sayName = obj.sayName;
// 取り出したsayNameのthisはグローバルオブジェクトを指している
sayName(); // => undefined
[ 成功例 ]
// 名前と、それを表示するメソッドを持つオブジェクトを作成する
var obj = {
name : 'matsuby',
sayName : function () {
console.log(this.name);
}
};
// オブジェクトからメソッドを取り出す際にbind()を使う(thisがobjを指す、新しい関数を作成する)
var sayName = obj.sayName.bind(obj);
// 取り出したsayNameのthisはobjを指している
sayName(); // => 'matsuby'
[ より具体的に ]
JavaScriptには非同期関数 + コールバック関数という仕組みが多用されます。
// 非同期な処理が終わったら引数の関数を実行する関数
function hoge (callback) {
// (※実際にはここに非同期な処理を書く)
// ↓ 完了したら引数の関数を呼び出し
callback();
}
// 実行時に無名関数を渡す
hoge(function () {
console.log('完了');
});
hoge関数の引数のcallbackがコールバック関数と呼ばれるものです。
ここで、以下のような形でオブジェクトのメソッドを渡すと失敗します。
// 名前と、それを表示するメソッドを持つオブジェクトを作成する
var obj = {
name : 'matsuby',
sayName : function () {
console.log(this.name);
}
};
// コールバック関数にメソッドを渡すと…
hoge(obj.sayName); // => undefined
いつの間にかthisがグローバルオブジェクトを指しているというパターンです。
そこでbind()を使ってthisを任意のオブジェクトに束縛することで解決出来ます。
hoge(obj.sayName.bind(obj)); // => 'matsuby'
参考:JavaScript の超便利なメソッド bind で this を制御する
まとめ
途中の解説はほぼ丸投げになってしまいましたが、思ったより長くなってしまいました。
コールバック関数は今後解説する予定ですが、少し触れてしまったり。
クラスベースにおけるthisは自分のクラス、親クラスの中で完結していましたが、
JavaScriptのthisは呼び出しのコンテキストによって参照先が異なっていたり、
apply,call,bindなどで任意のオブジェクトを束縛出来たりするなど、複雑です。
ですが、「グローバル汚染の防止」や「コールバック関数の制御」について理解するには
必要となってくる知識ですので、頑張って覚えましょう。
近況
最近、Effective JavaScriptを買いました。対象読者層は中上級者向けです。
JavaScript 第6版が「サイ本」ならば、こちらは「イス本」とでもいったところでしょうか。
本書はES5ベースで書かれており、巷で話題の(?)"use strict"
にも触れています。
個人的には、The Good Partsが「○○は悪いパーツだから使うべきではない」といった排他的な論調なのに対し、
イス本では「○○は便利で、強力だ。ただ、使う時には××といった注意点がある。使うのであれば、△△といった実装に留めた方が良い」といった風に中立的な論調であり、押しつけがましさが無い分、楽しく読めると思います。
それとなにより「コンストラクタ」を決して悪者として扱っておらず、
newを使わない場合のグローバル汚染の危険性についても、ファクトリメソッドとしての実装と併用することで
回避出来るといった道をちゃんと掲示している点が良書だと思いました。
(といってもまだ半分ほどしか読み終わってないのですが…。)
以上。