JavaScriptを勉強していて、誰しも一度はぶつかであろう壁があります。
それがthisです。
正直一度で理解できる人は極稀かと思います。
私は2、3度ぶち当たりました!!
今回は、そんなthisの真相を紐解いていき弄べるくらいになりましょう。
#thisとは?
thisとは呼び出し元のオブジェクトへの参照を保持するキーワードのことです。
thisの参照先は、関数の書き方で変わるのではなく、関数の呼び出し方法で変わることに注意してください。
つまり、同じ関数でも呼び出し方法が違えば、thisは異なるオブジェクトを参照します。
#関数メソッドとthis
const animal = {
name: 'dog',
introduce: function() {
console.log('The animal is ' + this.name);
}
}
animal.introduce(); // The animal is dog と出力
こちらはintroduceメソッド
の中で、thisが使用されています。
先ほども説明した通り、thisは呼び出し元のオブジェクトへの参照を保持しています。
なので今回のanimal.introduce();
内のthisは呼び出し元のanimalオブジェクト
を参照しています。
そのためthis.name
とするとanimalオブジェクト
のnameプロパティ
が取れてくるので、コンソールにはThe animal is dog
と表示されることになります。
そして、thisは実行コンテキストによって参照先が変わります。
const animal = {
name: 'dog',
}
console.log('The animal is ' + this.name);
// animalオブジェクトの外に定義すると The animal is と出力されます
この場合のthisは、windowオブジェクト
を参照しています。
windowオブジェクトにnameプロパティは存在していないので、The animal is
のみが出力されることになります。
次のように、windowオブジェクトにnameプロパティが存在していれば、その値を参照してくれます
window.name = 'dog' // windowオブジェクトにnameプロパティを追加
const animal = {
name: 'dog',
}
console.log('The animal is ' + this.name);
// The animal is dog と出力されます
#参照のコピーとthis
const animal = {
name: 'dog',
introduce: function() {
console.log('The animal is ' + this.name);
}
}
const animal2 = animal.introduce;
animal2() // The animal is のみ出力されます
今回は一度別の変数に代入してから実行しています。
この状態は変数animal2
にanimalオブジェクト
のintroduceメソッド
がコピーされている状態になります。
すなわち変数aninmal2
にはintroduceメソッドの関数
が代入されている状態です。
// このような状態と同等です
const animal2 = function() {
console.log('The animal is ' + this.name);
}
したがって関数を実行すると、thisはグローバルオジェクトを参照し、nameプロパティが存在しなければThe animal is
のみが出力されます。
window.name = 'dog';
const animal = {
name: 'dog',
introduce: function() {
console.log('The animal is ' + this.name);
}
}
const animal2 = animal.introduce;
animal2() // The animal is dog と出力されます
まとめると、オブジェクトのメソッドとして実行された場合には、thisは呼び出し元のオブジェクトを参照します。
一方で一度変数に代入すると、ただの関数となり実行された場合には、thisはグローバルオブジェクトを参照することになります。
もう一度コードを確認してみます。
window.name = 'dog' // windowオブジェクトにnameプロパティ定義
const animal = {
name: 'dog',
introduce: function() {
console.log('The animal is ' + this.name);
}
}
animal.introduce(); // The animal is dogと出力
const animal2 = animal.introduce;
animal2(); // The animal is dogと出力される。
animal.introduce();
とした時には、オブジェクトのメソッドとして実行されているため、thisは呼び出し元のanimalオブジェクトを参照することになります。
animal2();
とした時には、関数として実行されるため、windowオブジェクトのnameプロパティを参照することになります。
#コールバック関数とthis
const animal = {
name: 'dog',
introduce: function() {
console.log('The animal is ' + this.name);
}
}
function fn(callback) {
callback();
}
fn(animal.introduce); // The animal is のみ出力される
今回は関数fn
の引数としてanimal.introduceメソッド
を渡してみました。
そして関数fn
の中で関数を実行しています。
このとき、出力される値はThe animal is
のみとなっています。
これは関数fn
に渡されたのがintroduceメソッドの関数
であり、callback()
では渡ってきた関数を実行しているだけになるので、thisはグローバルオブジェクトを参照することになり、The animal is
のみが出力されます。
これは書き換えるとこのようなコードと同意であると言えます。
const animal = {
name: 'dog',
introduce: function() {
console.log('The animal is ' + this.name);
}
}
const animal2 = animal.introduce;
animal2() // The animal is のみ出力される
これは先ほどの「参照のコピーとthis」の章で出てきたコードと同じですね。
オブジェクトのメソッドをコールバック関数として呼び出した場合には、関数として実行され、thisはグローバルオブジェクトを参照します。
#bindとthis
bindとは「thisの参照先を指定できて新しい関数を生成する」メソッドです。
ではコードで確認していきます。
const animal = {
name: 'dog',
introduce: function() {
console.log('The animal is ' + this.name);
}
}
const animal2 = animal.introduce.bind(animal) // .bind(animal)でthisの参照先を指定
animal2();
先ほど説明した通り、bind
を使ってthisを束縛すると新しい関数として返却されることになります。
const animal = {
name: 'dog',
introduce: function() {
console.log('The animal is ' + this.name);
}
}
animal.introduce(); // The animal is dog と出力
const animal2 = animal.introduce.bind(animal)
animal2(); // The animal is dog と出力
console.log(animal.introduce === animal2) // 同じように見えるがfalseを返す
どちらもThe animal is dog
を結果として出力しますし、見た目は全く同じ関数なのですが、animal2
はbind
で作られた新しい関数ですので、animal.introduce
の関数とは別物であることがコンソールに出力された結果から分かるとおもいます。
#アロー関数とthis
アロー関数とはES6から使用できる、無名関数を省略化して記述する記法のことです。
const animal = {
name: 'dog',
introduce: () => { // 「 function() 」 を 「 () => 」 に変更した
console.log('The animal is ' + this.name);
}
}
person.hello(); // The animal is のみ出力される
今回、animal
のintroduceメソッド
をアロー関数に書き直してみました。
すると出力結果は、The animal is
のみとなってしまいました。
これは、アロー関数はthisを持たない性質が関係しています。
つまり、グローバルコンテキストで呼ばれたanimal.introduce
はthisを持っておらず、スコープチェーンをたどってwindowオブジェクトを参照することになります。
#まとめ
・thisは、呼び出し元のオブジェクトへの参照を保持する。
・thisは、関数として実行された場合にはwindowオブジェクトを参照する。
・bindやapplyを使うと、thisの参照先を指定できる。
・アロー関数ではthisを持っておらず、thisはスコープチェーンをたどってwindowオブジェクトを参照する。
以上
#宣伝
自分のTwitterでも有益な情報を発信していますので、興味があればフォローしていただけると幸いです。