スタイル・エッジ技術ブログ

士業集客支援/コンサルティングのスタイル・エッジのエンジニアによるブログです。

ちょっと周りで話題になったので、JavaScriptのthisについて改めて調べてみた

こんにちは、スタイル・エッジLABOのOです。今年の4月でついに3年目になりました。
最近業務中に、「JSで書いたはずのfunctionがnot definedになってとれない」と話が出まして、調査したところthisが悪さをしていたということがありました。

f:id:styleedge_tech:20200706092021j:plain
どうして。。
今となっても少しややこしいthisについて、今回書こうと思います(一人でも救われるように。。)。

JavaScriptのthis

JavaScriptを書く中で、個人的にどツボにハマる四天王として、
・this
・バブリング(イベント伝播)
・ホイスティング(宣言の巻き上げ)
・エラー文の意味のわからなさ(もはや愛嬌)
があります。
(prototypeとかもあったなあと書きながら)

今回は、この中でもとくにやっかいなthisについてです。
JavaScriptのthisはざっと4種類存在します。
・メソッド呼び出しのthis
・関数呼び出しのthis
インスタンス化時のthis
・bindするときのthis

メソッド呼び出しのthis

let obj = {
 val: 'hoge',
 fn: funcion () {
  console.log(this.val);
 }
};
obj.fn(); // hoge

このタイプのthisは、イメージ通り fn が定義されているオブジェクトを指します。

関数呼び出しのthis

let obj = {
 val: 'hoge',
 fn: funcion () {
  function fn2() {
    console.log(this.val);
  };
  fn2();
  console.log(this.val);
 }
};
obj.fn(); // undefined  hoge

そもそも関数とメソッドの違いは、定義されている場所です。
関数はグローバル空間に、メソッドはそのメソッドが書かれているオブジェクト(ローカル)に定義されます。
function hoge()と定義した時点で、それが例えオブジェクトのメソッド内であろうがグローバルに定義されます。
JavaScriptはそれ自体がObjectの塊なので、 定義されているオブジェクトをthisが指すという意味では根本的には一緒です。
グローバル空間に val というプロパティは存在しないので、例はundefinedになります。

インスタンス化時のthis

function Hoge(val) {
  this.val = val
  this.fn = function () {
   console.log(this.val);
  }
}
// インスタンス化
let foo = new Hoge('hoge');
foo.fn(); // hoge
// 関数呼び出し
Hoge('hoge'); // hoge
console.log(val); // hoge

インスタンス化した時は、thisはそのインスタンスを指しますが、もちろんそのまま呼んでしまえば関数呼び出しです。
this.val がグローバル空間に定義されるので、例の一番最後の val が hoge になっていると思います。
インスタンス化を想定したオブジェクトを定義する時は、最初の文字を大文字にしておくのが慣例のようです。

bindするときのthis

let hogeObj = {
 this.val: 'hoge',
 fn: function () {
  console.log(n);
 }
}
let fooObj = {
 this.val: 'foo';
}
hogeObj.fn(); // hoge
hogeObj.fn.call(fooObj) // foo

applyとかcallを使うときの第一引数にオブジェクトを指定することで、thisとして扱えるというものです。
便利ですが、わかってないで使うとひどい目にあいます(実体験)。

この応用に、htmlなどで使用するイベントリスナーからイベントを発火させるときのコールバックのthisがあります。

<button type="button" id="hoge">ボタン</button>
function hoge() {
 console.log(this);
}
document.getElementById('hoge').addEventListener('click', hoge);

イベントリスナーのコールバックとして呼ばれたときの関数でのthisは、そのリスナーの貼られているDOM(例でいうbutton)がthisとしてbindされます。
もし、ボタンを押した場合は、
<button type="button" id="hoge">ボタン</button>
がコンソールに表示されます。
前書きの、「JSで書いたはずのfunctionがnot definedになってとれない」という話は、これが原因でした。

以上が、JavaScriptのthisでした。

アロー関数

() => {} // こういう

同じ関数でも、アロー関数は少しthisの挙動が異なります。
アロー関数は、thisをbindしません。
つまり、

<button type="button" id="arrow">arrow</button>
<button type="button" id="normal">normal</button>
let obj = {
 hoge: function (e) {
  console.log(this);
 }
 foo: e => console.log(this);
};
document.getElementById('normal').addEventListener('click', obj.hoge);
document.getElementById('arrow').addEventListener('click', obj.foo);

というように定義したとき、hoge()とfoo()を発火させると以下のようになります。

obj.hoge(); // {hoge: ƒ, foo: ƒ}
obj.foo(); // グローバル(ブラウザだと、windowオブジェクト)

ブラウザで各ボタンを押すと、

<button type="button" id="arrow">arrow</button> // thisはグローバル(windowオブジェクト)
<button type="button" id="normal">normal</button> // thisは <button type="button" id="normal">normal</button>

になります。このように、アロー関数はどの位置で呼ばれてもグローバルをthisとします。

まとめ

今回は、JavaScriptのthisについてまとめてみました。
こういった部分をちゃんと理解するとJavaScriptはもっと面白くなりそうですね!
機会があったら他のどツボにハマる四天王についても根掘り葉掘り調べたいです。

スタイル・エッジLABOでは一緒に働く仲間を募集しております。 興味をお持ち頂けましたら是非とも下記をクリックしてください↓↓

recruit.styleedge-labo.co.jp