〽️ 外側の関数スコープを見ればよい。
〽️ 「解らない部分」をピンポイントで解説。
JavaScriptコードの「?」をまとめたページはこちら⬆︎。
@1@ 外側の関数スコープから見た「this」を参照する。
@2@ 関数スコープの中にいなければ、windowオブジェクトを指す。
こんにちは、「ふ」です。
前回の記事では、JavaScriptのthisの基本的な性質についてお話ししました。
〽️ 「メソッドかどうか」がポイント。
JavaScriptのthisキーワードは、「メソッドの場合にはそのオブジェクト、それ以外はwindowオブジェクトを指す」というのが基本なのですが、大きな例外も存在します。その1つが、アロー関数の中にあるthisです。
const func = (引数) => 処理
アロー関数のthisは、「定義時の関数スコープから見たthisを参照する」という性質を持っています。通常のthisとは違うふるまいをするので、扱う際には注意が必要です。
「関数スコープ?」
となった方もいるかもしれません。この記事ではアロー関数のthisを理解するために必要な関数スコープ/ブロックスコープの説明も含めて、解説していくとします。
また「アロー関数でthisを使うことのメリットは?」についても述べていきます。
「アロー関数とは?」となった方は、⬇︎の記事を参考にしてください。
〽️ 使い方とメリットについて解説。
オブジェクトのスコープと関数スコープについて確認しておきます。
<script> const a = 10; const b = 「「1{」」 a:20 「「1}」」; </script>
⬆︎のコードのうち、const bの波括弧{ }の中がオブジェクトのスコープです。
見てのとおり「オブジェクトbの内部」ということですね。
<script>〜</script>直下のグローバル空間で定数aを作っていますが、オブジェクトbのプロパティにもaというものがあります。
この「a」という名前の重複は、errorにはなりません。それぞれが置かれているスコープが別のものだからです。
定数aが置かれているのは、JavaScriptのグローバルなスコープ内です。プロパティaは、オブジェクトbのスコープ内です。アクセスする際には、
console.log(a); 「「3▶︎ 10」」 console.log(b.a); 「「3▶︎ 20」」
というようになります。定数aには単なる「a」、プロパティaには「b.a」とオブジェクト名をつなげる必要があります。この制約があるために、「a」という命名が重複しても問題はないのです。
オブジェクトのスコープである{ }は、ブロックスコープと呼ばれています。
次は関数スコープ。
「「1function A() {」」 let a = 30; let b = 40; 「「1}」」
関数スコープは、関数の内側の範囲のことです。
内部で変数aを作っていますが、これもグローバル空間の定数aと衝突することはありません。
function A() { let a = 30; let b = 40; console.log(b) 「「3// OK」」 } console.log(b) 「「4// error」」
関数スコープ内の値には、スコープ内部からでしかアクセスすることはできません。
⬆︎のコードではスコープ内では変数bにアクセスできているものの、外部であるグローバル空間からアクセスしようとするとエラーになります。
スコープ内で宣言した定数/変数には「内部からしかアクセスできない」という点でグローバル空間との差別化ができているんですね。
const B = 「「1function(」」b「「1) {」」 return b+5; 「「1}」」
関数スコープには、引数の部分も含まれます。
⬆︎の引数bは、関数Bのスコープ内にあることになります。
console.log「「1(」」"hello"「「1)」」;
オブジェクトのメソッドも、関数スコープを持っていることになります。
console.logはconsoleオブジェクトのlogメソッドですが、引数部分にある「hello」という文字列はlogメソッドの、関数スコープの中にいます。
ここまでオブジェクトのスコープと、関数スコープについて知りました。
そしてアロー関数のthisは、この2つのスコープに対して通常のthisキーワードとは違った挙動をみせます。
通常。メソッドのthisは、自身のオブジェクトを参照するものでした。
const a = { A:function(){console.log(「「1this」」)} }; a:A(); 「「3▶︎ {A:f}」」
⬆︎のコードで、プロパティAはaのメソッドです。
「this」を出力する処理になっていますが、これを実行するとオブジェクトaの内容がコンソールに表示されます。
ところがアロー関数のthisは、そうはなりません。
const b = { B:()=>console.log(「「4this」」) };
同じくthisを出力するメソッドを、アロー関数形式で書いてみました。
b.B(); 「「3▶︎ Window{....}」」
が、実行してみると。メソッド内のthisであるにもかかわらず、windowオブジェクトを参照しています。
アロー関数のthisはその対象を探しにいくときに、オブジェクトのブロックスコープを無視してしまうのです。
const v = 「「2{」」 w:「「5{」」 x:()=>console.log(this) 「「5}」」 「「2}」」 v.w.x(); 「「3▶︎ Window{....}」」
多重の入れ子にしたとしても、オブジェクトのスコープはことごとくすり抜け、グローバル空間にあるwindowオブジェクトを参照してしまいます💧
@1@ 外側の関数スコープから見た「this」を参照する。
冒頭でも記した、この法則。
アロー関数のthisを制御するのは、オブジェクトのスコープではなく、関数スコープなのです。
const b = { B:()=>console.log(this) };
先ほどwindowオブジェクトを参照していたアロー関数を、関数スコープの中で実行するように定義してみましょう。
const b = { B:function() { const func = ()=> console.log(this); func(); } };
メソッドBをフツーの関数スコープで定義。
その中でさっきのアロー関数を定数「func」として初期化、さらに実行させています。
b.B(); 「「3▶︎ {B:f}」」
外部からメソッドBを実行したところ、オブジェクトbにアクセスできました。
const b = { B:「「1function() {」」 const func = ()=> console.log(this); func(); 「「1}」」 };
このような結果が得られたのは、アロー関数を関数スコープの中に閉じ込めたからです。
@1@ アロー関数funcは、関数スコープであるfunction( ){....}の中にいます。
@2@ この関数スコープは、プロパティBそのもの。つまり、オブジェクトbのメソッドですね。
@3@「thisの基本法則」の記事でも述べたとおり、メソッドが参照するthisは、自身のオブジェクトでした。それにより、「console.log(this)」は、オブジェクトbを出力したのです。
アロー関数内のthisの立場から言うと「自身の外側にある関数スコープから見たthisを参照した」ことになります。
アロー関数のthisは、オブジェクトのスコープは無視。そのかわり、1つ外側にある関数スコープを元に、その対象をさがすのです。
@2@ 関数スコープの中にいなければ、windowオブジェクトを指す。
もう1つ、法則⬆︎がありました。
アロー関数を閉じ込める前のコードを見てみましょう。
const b = { B:()=> console.log(this) }; b.B(); 「「3▶︎ Window{....}」」
このアロー関数には「外側の関数スコープ」はありません。
「thisをさがす際の基準」となる関数スコープが見つからないため、1番外側にあるwindowオブジェクトを参照した、ということなんです。
アロー関数のthisが、外部の関数スコープを基準にするという性質。これにはどのようなメリット・使いどころがあるのでしょうか?
〜1つには、メソッド内で関数を実行する場合です。
const c = { num:10, func:function() { const C = function(){ console.log(this.num); } C(); } };
オブジェクトcには、numプロパティとfuncメソッドがあります。
funcメソッドの中で「this.num」を出力する関数Cを定義し、実行するようにしています。
ここで期待するのはnumプロパティの値である「10」ではないでしょうか。
c.func(); 「「3▶︎ undefined」」
残念ながらそうはなりません。funcメソッドを実行すると「undefined」となってしまいます。
func:「「4function() {」」 const C = function(){ console.log(this.num); } C(); 「「4}」」
通常関数のthisは、それがメソッドである場合のみ、自身のオブジェクトを参照するのでした。
⬆︎のコードにおいて、外側にある紫のfunction( ) {....}は、確かにメソッドです。ですが、内側のconst Cの定義はfuncメソッド内の1処理にすぎません。つまり、メソッドではない、ということです。
メソッドではないゆえに、const Cのthisはwindowオブジェクトを参照します。
「windowオブジェクトのnumプロパティは定義されていません」ということで、undefinedとなってしまったのです。
funcメソッドの内部で、自身のオブジェクトであるcを参照したい。
それには、「外側の関数スコープからthisを探してくれる」アロー関数の出番です。
const Cを書き換えてみましょう。
const c = { num:10, func:「「4function() {」」 const C = 「「1()=> console.log(this.num)」」 C(); 「「4}」」 };
このようにすれば、thisは外側の関数スコープである紫部分からその対象を探してくれるようになります。
紫スコープはconst cのメソッドなので、thisの対象は自身のオブジェクトです。
c.func(); 「「3▶︎ 10」」
無事、numプロパティにアクセスすることができました。
アロー関数のメリット1つ目は、「メソッド内の関数でもオブジェクト自身を参照できる」ところです。
メソッドのフツー関数にはもう1つ、問題があります。それは「引数内の関数ではwindowを参照してしまう」という点です。
const d = { func:function(){console.log(this)} }; d.func(); 「「3▶︎ {func:f}」」
オブジェクトdを作り、その中にfuncメソッドを定義しました。またもや!ですが、thisを出力する処理です。
これはメソッド直下のthisなので、自身のオブジェクトであるdにちゃんとアクセスしてくれます。
ところで。
このconsole.logの処理を時間差で行うとしたら、可能なのでしょうか。func2を作成し、setTimeoutの引数に取り入れてみます。
const d = { func:function(){console.log(this)}, func2:function(){ setTimeout(「「4this.func」」,2000); } }; d.func2(); 「「3▶︎ 2s後にwindow{....}」」
実行してみたところ、オブジェクトdではなくwindowオブジェクトが参照されました。 引数のところに、間接的にfuncを呼んだからでしょうか?
const d = { func3:function() { setTimeout(function(){ console.log(「「4this」」); },2000); } }; d.func3(); 「「3▶︎ 2s後にwindow{....}」」
こんどは引数内に直接処理を書いてみます。が、やはり自身のオブジェクトを参照することができません。メソッドのthisであるにも関わらず、です。
このように、メソッドの引数にフツー関数を取り込むと、それに含まれるthisはグローバルオブジェクトを指してしまうのです。不便ですね。
でもご心配なく。ここでまたアロー関数が活躍します。引数部分の記述を書き換えてみましょう。
const d = { func4:function() { setTimeout(()=>console.log(「「1this」」),2000); } };
前述のとおり、関数スコープは引数部分も含まれています。アロー関数のthisは、外側の関数スコープから見たthisを探すんでしたよね。
この関数スコープは、オブジェクトdのメソッドです。したがってthisが参照するのは、オブジェクトdということになります。
d.func4(); 「「3▶︎ 2s後に{func4:f}」」
実行すると、オブジェクトdの内容がconsoleに出力されました。
アロー関数のメリット2つ目は、「メソッド引数内の関数でもオブジェクト自身を参照できる」ところです。
最後までお読み下さり、ありがとうございます。
オブジェクトの内容を利用して何か処理をしたい、という事は多々あるでしょう。ところがフツー関数を入れ子にすると、グローバルオブジェクトを指してしまいます。
そんなときはアロー関数を上手く利用しましょう。
アロー関数のthis。少し複雑な内容でしたね。ただこれを知っておくと、Javascriptのオブジェクト操作を行う際に非常に役に立ちます。困った時には思い出してみて下さい。
ではまた〜🎵
ベクターグラフィック、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。