LoginSignup
0
0

More than 1 year has passed since last update.

【JavaScript】関数とthis

Posted at

概要

JavaScriptを学習、理解を深めるため「JavaScript Primer 迷わないための入門書」を読み、
理解した内容等を記載していく。

【JavaScript】JavaScript入門一覧」に他の記事をまとめています。

この記事で理解できること

  • JavaScriptにおけるthisの参照先、参照条件
  • 関数・メソッドにおけるthisの挙動
  • 関数・メソッドとベースオブジェクトの関係

関数とthis

thisの参照先は、主に以下の条件によって変化するとされている。

  • 実行コンテキストにおけるthis
  • コンストラクタにおけるthis
  • 関数とメソッドにおけるthis
  • Arrow Functionにおけるthis(以降、Arrow Function = アロー関数)

実行コンテキストとthis

  • JavaScriptには実行コンテキストとして、ScriptModuleが存在する。
  • もっとも外側のスコープにあるthisは、実行コンテキスト(ScriptModule)によって値が異なる。
  • 単純にグローバルオブジェクトを参照したい場合は、thisではなくglobalThisを使う(ES2020で導入)

スクリプトにおけるthis

  • script要素のtype属性を指定していない場合、ブラウザでは実行コンテキストがScriptとして実行される。
  • そのscript要素の直下に書いたthisはグローバルオブジェクトであるWindowオブジェクトとなる。
  • 開発者コンソールでconsole.log(this)と出力してみるとWindowオブジェクトが出力される。

スクリーンショット 2022-06-13 6.14.17.png

モジュールにおけるthis

  • script要素にtype="module"属性がついた場合は、実行コンテキストがModuleとして実行される。
  • (HTMLを開発者コンソールで確認してみると)script要素の直下に書いたthisundefinedとなる。
<!-- 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の基本的な参照先はベースオブジェクトとなる。
  • ベースオブジェクトとは、メソッドを呼ぶ際に、そのメソッドのドット演算子またはブラケット演算子のひとつ左にあるオブジェクトのことを言う。
  • ベースオブジェクトがない場合のthisundefinedとなる。
"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] }

関数宣言や関数式

ベースオブジェクトの例で記載したとおり、関数宣言や関数式には以下のような特徴がある。

  • 関数宣言や関数式のベースオブジェクトは存在しない。
  • 関数におけるthisundefinedとなるため使い道がない。
"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

  • コールバック関数はただの関数であるためthisundefinedとなる。
    (関数宣言や関数式はベースオブジェクトが存在しないため、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さん' ]
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0