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でも有益な情報を発信していますので、興味があればフォローしていただけると幸いです。