0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【JavaScript】thisを弄ぼう

Last updated at Posted at 2021-06-01

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 のみ出力されます

今回は一度別の変数に代入してから実行しています。
この状態は変数animal2animalオブジェクト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を結果として出力しますし、見た目は全く同じ関数なのですが、animal2bindで作られた新しい関数ですので、animal.introduceの関数とは別物であることがコンソールに出力された結果から分かるとおもいます。

#アロー関数とthis

アロー関数とはES6から使用できる、無名関数を省略化して記述する記法のことです。

const animal = {
  name: 'dog',
  introduce: () => { //  「 function() 」 を 「 () => 」 に変更した
    console.log('The animal is ' + this.name);
  }
}
person.hello(); // The animal is のみ出力される

今回、animalintroduceメソッドをアロー関数に書き直してみました。
すると出力結果は、The animal isのみとなってしまいました。

これは、アロー関数はthisを持たない性質が関係しています。
つまり、グローバルコンテキストで呼ばれたanimal.introducethisを持っておらず、スコープチェーンをたどってwindowオブジェクトを参照することになります。

#まとめ

・thisは、呼び出し元のオブジェクトへの参照を保持する。
・thisは、関数として実行された場合にはwindowオブジェクトを参照する。
・bindやapplyを使うと、thisの参照先を指定できる。
・アロー関数ではthisを持っておらず、thisはスコープチェーンをたどってwindowオブジェクトを参照する。

以上

#宣伝
自分のTwitterでも有益な情報を発信していますので、興味があればフォローしていただけると幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?