概要
JavaScriptを学習、理解を深めるため「JavaScript Primer 迷わないための入門書」を読み、
理解した内容等を記載していく。
「【JavaScript】JavaScript入門一覧」に他の記事をまとめています。
この記事で理解できること
- JavaScriptにおけるthisの参照先、参照条件
- 関数・メソッドにおけるthisの挙動
- 関数・メソッドとベースオブジェクトの関係
関数とthis
this
の参照先は、主に以下の条件によって変化するとされている。
- 実行コンテキストにおけるthis
- コンストラクタにおけるthis
- 関数とメソッドにおけるthis
- Arrow Functionにおけるthis(以降、Arrow Function = アロー関数)
実行コンテキストとthis
- JavaScriptには実行コンテキストとして、
Script
、Module
が存在する。 - もっとも外側のスコープにある
this
は、実行コンテキスト(Script
、Module
)によって値が異なる。 - 単純にグローバルオブジェクトを参照したい場合は、
this
ではなくglobalThis
を使う(ES2020
で導入)
スクリプトにおけるthis
- script要素のtype属性を指定していない場合、ブラウザでは実行コンテキストが
Script
として実行される。 - そのscript要素の直下に書いた
this
はグローバルオブジェクトであるWindowオブジェクト
となる。 - 開発者コンソールで
console.log(this)
と出力してみるとWindowオブジェクト
が出力される。
モジュールにおけるthis
- script要素に
type="module"
属性がついた場合は、実行コンテキストがModule
として実行される。 - (HTMLを開発者コンソールで確認してみると)script要素の直下に書いた
this
はundefined
となる。
<!-- htmlのbody要素などに以下をscript要素をModuleコンテキストとして記載 -->
<script type="module">
console.log(this); // => undefined
console.log(globalThis); // => ブラウザの場合は、Windowsオブジェクトを参照するようになる
</script>
関数とメソッドにおけるthis
-
this
が参照先を決めるルールは、アロー関数
とそれ以外の関数定義
の方法で異なる。
【おさらい】関数・メソッドの定義、呼び出し方法
関数の定義方法の種類
関数の定義方法には以下の3つがある
- functionキーワードを使用する
- 関数式
- アロー関数(式)
// functionキーワードを使用する
function func1() {
// 処理
}
// 関数式
const func2 = function() {
// 処理
}
// アロー関数(式)
const func3 = () => {
// 処理
}
// "関数名()"で呼び出し
func1();
func2();
func3();
メソッドの定義方法の種類
- JavaScriptでは
オブジェクトのプロパティ
として定義されている関数をメソッド
と呼ぶ。
const obj = {
method1: function() {
// 処理
},
// アロー関数
method2: () => {
//処理
},
// 短縮記法
method3() {
// 処理
}
}
// "オブジェクト.メソッド名()"でメソッドの呼び出し
obj.method1()
obj.method2()
obj.method3()
アロー関数以外の関数におけるthis
- アロー関数以外の関数における
this
の値は関数を実行するとき
に決まる。 -
「関数を実行するときに決まる」
背景として、後述のベースオブジェクト
が関係している。 - 暗黙的に関数に
this
が渡され、ベースオブジェクトの状況
によってthis
の内容が変わるイメージ。
// 以降、意図しにくい動作を防止するため"use strict";を明示的に記載する
"use strict";
// 暗黙的にthisは渡ってくる
function func() {
console.log(this);
}
func(); // => undefined
ベースオブジェクト
- 関数におけるthisの基本的な参照先は
ベースオブジェクト
となる。 - ベースオブジェクトとは、メソッドを呼ぶ際に、そのメソッドの
ドット演算子
またはブラケット演算子
のひとつ左にあるオブジェクト
のことを言う。 - ベースオブジェクトがない場合の
this
はundefined
となる。
"use strict";
// ★1 関数(宣言)
function func() {
console.log(this);
}
// ★2 オブジェクトのメソッド
const obj = {
method: function() {
// 処理
console.log(this);
}
}
// ★1 ベースオブジェクトはない
func(); // => undefined
// ★2 ドット演算子またはブラケット演算子のひとつ左にある「obj」がベースオブジェクト
obj.method(); // => { method: [Function: method] }
obj["method"](); // => { method: [Function: method] }
関数宣言や関数式
ベースオブジェクトの例で記載したとおり、関数宣言や関数式には以下のような特徴がある。
- 関数宣言や関数式のベースオブジェクトは存在しない。
- 関数における
this
はundefined
となるため使い道がない。
"use strict";
// ★1 関数(宣言)
function func() {
console.log(this);
}
// ★1 ベースオブジェクトはない
func(); // => undefined
メソッド呼び出しにおけるthis
メソッドには以下のような特徴がある。
- JavaScriptでは
オブジェクトのプロパティとして指定される関数
のことをメソッド
と呼ぶ。 - 結果、メソッドは、
何かしらのオブジェクトに所属している
ことになるためベースオブジェクト
は存在する。
"use strict";
// ★2 オブジェクトのメソッド
const obj = {
method: function() {
// 処理
console.log(this);
}
}
// ★2 ドット演算子またはブラケット演算子のひとつ左にある「obj」がベースオブジェクト
obj.method(); // => { method: [Function: method] }
obj["method"](); // => { method: [Function: method] }
結果的にメソッド内で以下のような参照が可能となる。
"use strict";
// オブジェクトのメソッド
const obj = {
type: "オブジェクト",
method: function() {
// 処理
console.log(this.type); // this.typeは、ベースオブジェクト「obj」のtypeプロパティを指定していることになる
}
}
obj.method(); // => オブジェクト
thisが問題となるパターン
- 代表的な2つの例として
thisを含むメソッドを変数に代入した場合
とコールバック関数とthis
がある。
thisを含むメソッドを変数に代入した場合
"use strict";
const obj = {
type: "オブジェクト",
method: function() {
// 処理
console.log(this.type);
}
}
// 変数nonObjMethodにobj.methodを代入(ただの関数となりベースオブジェクトも存在しなくなる)
const nonObjMethod = obj.method;
// ベースオブジェクト、typeというプロパティも存在しないので例外が発生する
nonObjMethod(); // => Cannot read properties of undefine
コールバック関数とthis
- コールバック関数はただの関数であるため
this
はundefined
となる。
(関数宣言や関数式はベースオブジェクトが存在しないため、thisは「undefinedになる
」と先ほど学びましたね) - 対処法としては、
this
を一時的に変数に代入する。
"use strict";
const human = {
suffix: "さん",
memberCall(members) {
return members.map(function(member) {
// (コールバック)関数におけるthisはundefinedなのでsuffixプロパティも存在しない
return member + this.suffix;
});
}
};
const members = human.memberCall(["Taro", "Hanako", "Boby"]); // => Cannot read properties of undefined
console.log(members);
下記のように対処することができる。
"use strict";
const human = {
suffix: "さん",
memberCall(members) {
// thisを一時的に変数に代入 -> that(this)はmemberCallにおけるthis -> thisはベースオブジェクトのhuman
const that = this;
return members.map(function(member) {
// `this`ではなく`that`を参照する
return member + that.suffix;
});
}
};
const members = human.memberCall(["Taro", "Hanako", "Boby"]);
console.log(members); // => [ 'Taroさん', 'Hanakoさん', 'Bobyさん' ]
アロー関数とthis
- アロー関数で定義された関数やメソッドにおける
this
がどの値を参照するかは関数の定義時(静的)
に決まる。 - アロー関数とそれ以外の関数との大きな違いは、アロー関数は
thisを暗黙的な引数として受けつけない
こと。 - アロー関数には
this
が定義されておらず、常に外側のスコープを参照する(スコープチェーン)
という特徴がある。
"use strict";
const human = {
suffix: "さん",
memberCall(members) {
return members.map((member) => {
// アロー関数で記載すると外側のスコープを参照 -> memberCallにはthisがないのでさらに外側のhumanオブジェクトを参照
return member + this.suffix;
});
}
};
const members = human.memberCall(["Taro", "Hanako", "Boby"]);
console.log(members); // => [ 'Taroさん', 'Hanakoさん', 'Bobyさん' ]