こちらは、 CAMエンジニア Advent Calendar 2019 12日目の記事です。
昨日は maruken24 さんの レンダリングパターンをいらすとやで解説してみた でした。
こんにちわ!KeitaroArataです。
Qiita初投稿させていただきます。
今年の春に新卒フロントエンジニアとして入社させていただき、
Web未経験ですがサービスに携わらせて頂いております。
駄文ですが、最後まで読んでいただき、ご指摘などいただけると嬉しいです。
概要
半年ほど前にthisが変わる問題
みたいなことを先輩が言っていたことを思い出しました。
どうやら、メソッドの中でthisを使うと、想定とは違った挙動をすることがあるようです。
今回はその問題に対して目を向けていこうと思います。
thisで呼んでいるのに…
this.__nickname = "kebosu"
const object = {
console: function() {
console.log(this.__nickname);//undefined
}
}
object.console();
上記のようなコードなんですが、みなさんこちらって何が帰ってくると思いますか…?
僕はなんとなくで書いたんですけど、これってundefined
が返ってくるんですよ。不思議ですね。
直感的にはkebosu
が返ってくる事が期待されるこのコード。
さて、この問題を解決し、kebosu
を出力させるには一体どうしたら良いのでしょうか。
まずは、この出力しているthisは一体何者なのかを調べる必要がありそうです。
console.log(this);
をsetTimeout内のfunctionに入れてみると…
Object {console: function(...)}
と、objectの中身が出力されました。
おや?ということは
理想:グローバルオブジェクトを参照
現実:オブジェクトの中身を参照
となっているというわけですね。
さて、ここでthisという曖昧なものをはっきりとさせましょう
thisとは
this
とは呼び出される場所によって姿を変える独特な変数のことです。
- 関数内のthisは,グローバルオブジェクトのままである
- メソッド内のthisは,そのメソッドが属するオブジェクトを指す
- コンストラクタから呼び出したthisは,そのコンストラクタが生成したオブジェクトを指す
大体のthisが上記の三パターンに分類できるそうです。
(ちなみに僕ここで初めて知ったんですが、関数とメソッドって別物なんですね…)
12/12追記:別物というか、関数の一部をメソッドと呼ぶ感じですね。
さらにthisを操作する方法として、bind
というものがあります。
bind
とは、thisの参照先を変更する事ができる関数です。
this.__nickname = "kebosu"
const object = {
console: function() {
console.log(this.__nickname);//kebosu
}.bind(this)
}
object.console();
と、bindしてあげることで、無事kebosu
が出力されましたね。
thisの参照先を指定してあげることで問題は解決しました。
万事解決と言いたいところですが、上記のコードだった場合は、わざわざbindしてあげる必要もないのです。
this.__nickname = "kebosu"
const object = {
console: () => {//arrow function
console.log(this.__nickname);//kebosu
}
}
object.console();
このように、アロー関数を使ってあげれば良いのです。
アロー関数
ざっくりいうと関数式(関数リテラル)をより簡潔にかける記法のことです。
アロー関数というのは具体的には下記のようなものです。
//es6
const hoge = () => {
console.log("hoge")
}
hoge();//"hoge"が出力;
これはes6から使えるようになった記法です。
ちなみにコレをes5に変換すると↓
//es5
var hoge = function hoge() {
console.log("hoge");
};
hoge();//"hoge"が出力;
見比べてみると、アロー関数の方が簡潔に記述されている印象があり、スッキリした書き方ですね。
引数にfunction
が入るもの(map
、foreach
等)にもこの記法は有効です。
という、上記程度の知識でしかアロー関数式というものを認識していませんでした。
この機会にMDNを見てみると
アロー関数式は、より短く記述できる、通常の function 式の代替構文
最初に言っていた、簡潔な書き方というのは正しいようですね。安心しました。
しかし、さらに読み進めて行くと…
アロー関数自身は this を持ちません。レキシカルスコープの this 値を使います。
レキシカルスコープというワードが出てきましたね。
次にレキシカルスコープの話をしましょう。
レキシカルスコープ
まず、ローカル変数の有効範囲を決める概念としてレキシカルスコープとダイナミックスコープというものがあります。
・レキシカルスコープ...関数を定義した時点でスコープが決まる
・ダイナミックスコープ...関数を呼び出した時点でスコープが決まる
言語ごとにスコープの仕様は決まっているようで、JavaScriptはレキシカルスコープです。
前章の話と繋げると、アロー関数式は関数を定義した時点でのthisを用いるようです。
つまりは__アロー関数式を定義した際のスコープでthisが決まる__ということですね。
const object = {
num: 1,
fnA: function fnA(){console.log(this.num)},
fnB: () => {console.log(this.num)}
}
object.fnA();//1
object.fnB();//undefined
こちらを見ていただけるとわかりやすいですが、従来のfunctionとアロー関数で挙動が違うことがわかると思います。
・function: 内部のthisは実行時のレシーバであるオブジェクトになる
・アロー関数: 内部のthisは宣言時のスコープを持つオブジェクトになる
今回の例だと、object
がレシーバオブジェクトであり、functionではobject
内のnumがthisの参照先になっているということですね。
一方、アロー関数は参照がグローバル(window)オブジェクトになっているため、windowオブジェクトのnumを参照しようとしていますが、定義されていないためundefined
が返ってきています。
そのため、varで変数を用意してあげると↓
const object = {
num: 1,
fnA: function fnA(){console.log(this.num)},
fnB: () => {console.log(this.num)}
}
var num = 2;
object.fnA();//1
object.fnB();//2
のような結果になります。
this、奥がふかいですね。
まとめ
・thisとは呼び出した場所によって中身が変わる変数
・アロー関数とはes6から生まれた、関数宣言を簡潔にかける方法である。
・jsはレキシカルスコープである。
・アロー関数はthisの参照元が宣言時にbindされる
最後に
今回、thisについてお話ししましたが、setTimeoutと絡めてあげたらどんな挙動をするのかだったり、クロージャの話など、展開できる話題は多くあります。
今後も機会があれば記事にまとめて、あとで見返せるような形で知識を広げていきたいです。
今回は、初の記事ということで、稚拙な文章を見せてしまったかもしれないですが、ここまで読んでいただき誠にありがとうございました。
明日は @kita21 の記事になります
お楽しみに!
参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions
https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures
https://qiita.com/shoichiimamura/items/aec874fc5cf55cde7fa1
https://qiita.com/mejileben/items/69e5facdb60781927929
https://qiita.com/t-tonchim/items/07d763325c7cb659efb7
https://wemo.tech/904
http://enlosph.hatenablog.com/entry/2017/01/20/222605