〽️ 外側の関数スコープを見ればよい。
〽️ 「解らない部分」をピンポイントで解説。
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、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。