JavaScript 関数の基礎とthis
今回JavaScriptの基礎を振り返り学習し、特に記憶が曖昧な部分や始めて知った新たな知識を備忘録として残しておきます。
まずは基本的な定義の仕様から、、
関数の定義について
JavaScriptで関数を定義する方法は主に次の二種類があります
- 関数宣言による定義
- 関数式による定義
硬い言い方をしましたが関数宣言とはいわゆる、functionから始まる書式です
function 関数名 (引数,,,){ 実行コード }
関数宣言での定義の場合、同じ関数名で新しく定義した場合、あとから宣言した方の処理に上書きされます
これに対して関数式は代入演算子を使って変数に関数を代入する形です
const 関数名 = function(引数,,,){ 実行コード }
一見すると違いが分からないかもしれませんが、関数宣言にて定義された関数はグローバルスコープです
つまり変数varで宣言したのと同じでトップクラスの有効範囲を持つため、バグになる可能性が高くなってしまいます
もっとも、モジュールを有効化している場合はこの方法でもブロックスコープになるようで安心です
Functionコンストラクタによる関数定義
これは使用することはなさそうですが一応。。。
JavaScriptの組み込みコンストラクタ関数にて宣言する方法です
特徴としては関数を文字列にて宣言することが可能になります
Functionコンストラクタ記法
- const 関数名 = new Function( “引数”, , , “return val1 + val2”);
- 引数: 引数は文字列で定義します<br>本文: 最後の文字列が、関数の本文となります。本文も文字列で定義します
すべて文字列にて定義できるため関数を動的に作成することも可能ですが、セキュリティー上の問題も多いらしく初心者は使用しないほうが良さそうです
同じ理由で、eval関数
というメソッドがあるようですがこちらの仕様も控えましょう、、、
デフォルト引数
function (arg1 = 初期値, arg2) {....}
上記の例のように引数に = でデフォルトの値を持った引数を使うことが出来ますが、
デフォルト引数が設定されるのは引数が undefined の時だけで null の場合はデフォルト引数が設定されないため間違えないようにしましょう
引数にオブジェクトを使用する場合
引数の種類が多くなってきた場合にはオブジェクトとして引数をまとめることもあるかと思います
PHPなんかでもありましたね
function fn (obj) { obj.arg1 ??= "初期値1"; obj.arg2 ??= "初期値2"; Null合体の自己代入を使用 console.log(obj.arg1 + obj.arg2); } const params = {arg2: "実引数"}; fn(params);
JavaScriptの関数の引数としてオブジェクトを使用する際の注意点ですが
変数に格納されている値は対象のアドレスであるため、関数の中でオブジェクトのプロパティの値に変更を加えると、元のオブジェクトのプロパティにも影響が出てしまいます。そこで、関数の中で新たにオブジェクトを設定すると良さそうです
const obj = {val: 1}; function fu(obj2) { obj2 = {val: 2}; //別のオブジェクトを設定 } fu(obj); console.log(obj.val); >1 //元のオブジェクトに変化なし
アロー関数について
ES6のJavaScriptといえばこれ
いい機会なので総まとめとして残します
アロー関数とは「無名関数の省略記法」のことで、引数の個数や関数本文の行数によって省略できる度合いが変わってきます
それぞれのパターンで見ていきます
- ①引数がない場合
- () => { 関数の本文 };
- ②引数が1つの場合
- 引数 => { 関数の本文 };
- ③引数が複数の場合
- ( 引数1, 引数2, … ) => { 関数の本文 };
- ④関数の実行分が一行の場合
- ( 引数1, 引数2, … ) => 関数の本文; ※そのまま戻り値になります
- ⑤関数の実行文一行かつ戻り値がオブジェクトの場合
- ( 引数1, 引数2, … ) => ( { プロパティ1: 値1, プロパティ2: 値2} );
これ以外は省略できません
キーワード | 無名関数 | アロー関数 |
this | thisを持つ | thisを持たない |
arguments | argumentsを持つ | argumentsを持たない |
new | newでのインスタンス化が可能 | newでのインスタンス化が不可能 |
prototype | prototypeを持つ | prototypeを持たない |
argumentsは関数実行時に渡される実引数を保持する配列風のオブジェクトで、アロー関数以外を実行した時に関数内で自動的に使用できる状態になっていますが、
ES6からスプレッド構文で可変長引数の処理を行えるため、これに関して覚える必要はなさそうです
thisの挙動について
アロー関数に関連深い this
の挙動についてここでまとめておきます
特にオブジェクトやクラスなどでよく目にする this ですが、JavaScriptの実行環境であるコンテキストという概念から紹介していきます
実行コンテキストとは
実行コンテキストとはコードが実行される際にJavaScriptエンジンによって準備されるコードの実行環境のことです
これが生成されるタイミングは主に二種類
- HTMLのscriptタグの直下やJavaScriptファイルの直下に記述されたコードが実行される直前
- 関数が実行される直前
①では「グローバルコンテキスト」が生成され、②は「関数コンテキスト」というコンテキストがそれぞれ生成されます
そしてそれぞれのコンテキスト内には以下の情報が保持されます
- そのコンテキスト内で宣言された変数や関数
- レキシカルスコープの変数や関数
- その他の使用できる変数やキーワード
- this の参照先
つまり関数を実行するとそのたびに実行コンテキストが違うためthisの参照先も変わるということです
そして「グローバルコンテキスト」はトップレベルの実行環境でwindowオブジェクトなどもここで使用可能になるわけで、当然このコンテキストはでthisはwindowオブジェクトを指します
※globalThisというキーワードを見かけることがありますが、これは実行環境に応じたグローバルオブジェクトを取得するための識別子です
JavaScriptの実行環境が多岐にわたるため、例えば現在勉強しているブラウザ環境ならwindowオブジェクトですし、ブラウザ内部の特殊な実行環境ではseltという識別子になります。またNode.jsの環境ではglobalという識別子になります。
このように環境をまたいで動作するコードを記述する際にとても面倒であることから、ES2020からglobalThisというキーワードでJavaScriptが実行される環境に応じたグローバルオブジェクトを取得できるようになったようです
関数コンテキストのthis
関数コンテキスト内のthisは関数の実行の仕方によって参照する先の値が異なります
大きく二種類のパターンです
オブジェクトのメソッドとして実行した場合
Object.method();
とこのようにドット記法でオブジェクトから実行する場合は、this の参照先は「メソッドが格納されているオブジェクトです」
関数として実行した場合
関数名(); として実行した場合はthisは「Windowオブジェクトを指します」
※ただし、Strictモード “use strict”;が有効になっている場合はthisはundefinedになるため注意
コールバック関数におけるthisの参照先
ここらへんがややこしい、、
先の説明で「オブジェクトのメソッドとして実行される関数内のthisの参照先は、呼び出し元のオブジェクト」と説明しましたが
「オブジェクトのメソッドをコールバック関数として異なる関数に渡した場合」を見てみましょう
window.name = "おジャコ丸"; const taro = { name: "太郎", hello: function () { console.log("こんにちは、" + this.name); }, }; function greeting(callback) { callback(); } greeting(taro.hello);
この場合コンソールには「こんにちはおジャコ丸」と表示します
オブジェクトのメソッドなのでオブジェクトのプロパティである「太郎」が取得されそうですが、
実際はWindowオブジェクトの値が取得されています
この理由は参照している先の関数がコールバック関数として引数に渡りcallback();として実行されているためです
また同様のことは、オブジェクトのメソッドを代入したときにも発生します。
アロー関数内でthisが使われた場合
先の表で簡単に説明しましたが、アロー関数が実行されたときの関数コンテキストには、thisが存在しませんそのため、アロー関数内でthisが使われた場合にはスコープチェーンをたどって、レキシカルスコープ (実行中のコードの外側) に対してthisを探しにいきます、そしてその時最初に見つかったthisが、アロー関数thisキーワードの参照先として使われます
クラスの中のthis
他のプログラミング言語と同じく、JavaScriptにおいてもクラスをnew演算子でインスタンス化した場合に参照されるのはもちろん自身のインスタンスを参照します
thisの束縛
ここからはthisを束縛する方法について記していきます
thisを扱うということはJavaScriptでは必須となっているため、ここの理解は必然だと思います
thisを特定の値に固定 (束縛) するためには、bind、apply、call、という3つのメソッドを使います
※しかし前項で紹介したアロー関数はそもそもthisを持たないため、これらのメソッドでも束縛することは出来ません
bindメソッド
bindメソッドを使うと、thisの参照先を自由に変更できます
以下はbindメソッドの記法です
bindの記法
- const bindFn = fn.bind( obj[, arg1, arg2 , … ]);
- fn : this、または引数を束縛したい関数かメソッドを指定<br>obj : 関数fn内のthisの参照先にしたいオブジェクトを指定<br>arg1, arg2 : bindでは関数fnに渡す引数も指定できる<br>bindFn : bindによってthisまたは引数が束縛された新しい関数が返る
bind() は関数を実行するのではなく関数のthisと引数を固定した新しい関数を作成しているだけです
bindメソッドの利用ケース
bindメソッドが主に使われるのはコールバック関数として渡す関数のthisや引数を束縛したい場合です
例えば先に紹介しましたがオブジェクトのメソッドをコールバック関数として渡そうとするとthisの参照先がWindowオブジェクトになってしまうため、意図した動作になりません
そういった場合にthisを固定すると開発の意図通りの挙動にすることが出来ます
callメソッドとapplyメソッド
上記で解説したbindメソッドは新たに関数を生成するのみですがcallメソッドは同様に関数を作成し即時に実行します
callの記法
- fn.call( obj[, arg1, arg2 , … ]);
- fn : this、または引数を束縛したい関数かメソッドを指定<br>obj : 関数fn内のthisの参照先にしたいオブジェクトを指定<br>arg1, arg2 : callでは関数fnに渡す引数も指定できる
callメソッドはbindメソッドと同じ形式で引数を渡します
bindメソッドと異なるのはcallメソッドを呼び出した時点でthisや引数が束縛された関数が実行される点です
applyメソッド
applyの記法
- fn.apply( obj[, array ]);
- fn : this、または引数を束縛したい関数かメソッドを指定<br>obj : 関数fn内のthisの参照先にしたいオブジェクトを指定<br>array : applyメソッドの第二引数には配列として、関数fnの引数を渡します
まとめ
ここまで関数の扱い方でわからない部分や改めて深堀りしたthisの挙動をまとめておきました
今まであまり使用してこなかった部分ですが上手くまとめて、上手く流用して使いこなすまたは、読解力になればと思い執筆してきました
お疲れさまでした
コメントをお待ちしております