JavaScriptの関数について纏めてみました。
勉強中なので間違っていたら申し訳ないです。ご指摘頂ければ幸いです。
関数の基礎
●関数宣言文による関数の定義
function hoge(a) {return a*a};
この関数は仮引数をaとし、引数で受け取った値を2乗して返します。
この関数の呼び出し方は以下となります。
hoge(2); // 4
●関数宣言文の巻き上げ
関数宣言文は変数宣言文と同じようにプログラムの先頭に巻き上げられます。
なので、以下のような場合でもエラーとはなりません。
console.log(hoge(2)); // 4
function hoge(a) {return a*a};
●値としての関数
関数もオブジェクトです。宣言すると関数名を変数名とする変数が生成され、関数オブジェクトへの参照が値へ格納されます。
詳しくはオブジェクトの記事を別途出します。
var hg = hoge;
console.log(hg(2)); // 4
●値渡しと参照渡し
関数の引数にプリミティブ型を渡す場合とオブジェクトを渡す場合で挙動が異なってきます。
var a = 2;
var b = hoge(a);
console.log(a); // 3
console.log(b); // 4
引数がプリミティブの場合、変数aの値のコピーが関数の引数に代入されます。
なので、元の変数aの値は変更されません。
function huga(p) { p.x++; p.y++; return p;};
var a = {x:3, y:4};
var b = huga(a);
console.log(a,b); // { x: 4, y: 5 } { x: 4, y: 5 }
引数にオブジェクトを指定すると、参照値が引き渡されます。
この場合、変数aと引数pは同じオブジェクトを参照しています。
結果として、p.xやp.yを変更することはa.xやa.yを変更していることと同じです。
●関数リテラルによる関数の定義
関数は関数リテラルでも定義することができます。
var huga = function huga(p) { p.x++; p.y++; return p;};
function(){...}の部分が関数リテラルです。匿名関数や無名関数とも呼ばれます。
使い方は同じですが、関数リテラルによる関数定義は巻き上げが行われません。
なので以下はエラーとなります。
var a = {x:3,y:4};
console.log(huga(a)); // エラー
var huga = function huga(p) { p.x++; p.y++; return p;};
関数の定義
関数の定義方は以下の4通りあります。
function hoge(x) { return x*x; };
var hoge = function(x) { return x*x; };
var hoge = new Function("x", "return x*x");
var hoge = x => x*x;
関数の呼び出し
関数を呼び出して実行する方法は以下の4通りあります。
var a = hoge(2);
// オブジェクトのプロパティ値が関数への参照である時、そのプロパティはメソッドです。
obj.p = function(x) { return x*x; };
obj.p(2);
var a = new hoge();
// これだけだと何だよと思うかもですが。
hoge.call(thisArg);
即時関数
無名関数を通常実行するには、以下でした。
var f = function() {...};
f();
これを定義時に実行する構文が「即時関数」です。こう書きます。
(function() {...})();
(function() {...}());
// 引数を渡すには
(function(a) {...})(2);
これらは、グローバルスコープを汚染しない名前空間を生成する為に利用します。
関数の引数
引数の省略
実引数が省略された仮引数にはundefinedが設定される。
function hoge(a,b) {
console.log(a);
console.log(b);
}
hoge(2); // 2 undefined
Argumentsオブジェクト
すべての関数でローカル変数として利用可能。関数が実引数3つで呼び出されたとすると
以下のようにargumentsに値が格納されます。
arguments[0] // 1番目の実引数の値
arguments[1] // 2番目の実引数の値
arguments[2] // 3番目の実引数の値
また、Argumentsにはlengthとcalleeというプロパティをもつ
それぞれ、lengthが実引数の数、calleeが現在実行中の関数への参照をもつ
callオブジェクトとローカル変数
関数の呼び出しを行うと、現在実行しているコードの処理は一旦停止し制御が呼び出した関数に移ります。
すると、はじめにローカル変数を格納する変数オブジェクトを生成します。関数コードにおいての変数オブジェクトをcallオブジェクトと言います。callオブジェクトはプログラムからアクセスできません。
また、以下のプロパティを持ちます。
- 関数の仮引数
- 関数内の関数宣言文で定義されたローカル変数
- 関数内でvarで宣言されたローカル変数
- arguments
- 呼び出し元の変数オブジェクトへの参照
callオブジェクトの生成後はthisの値が決まります。
その後にコードが実行されます。
コード実行時にはすでにローカル変数や関数宣言文で宣言された関数は生成されているので、
利用できます。これが変数や関数宣言の巻き上げ(ホイスティング)の正体です。
thisの値
thisの値は関数が呼び出された時に、その関数が所属していたオブジェクトへの参照です。
var car = {
name: "日本車",
drive: function() {
console.log("car is " + this.name);
}
};
car.drive(); // "car is 日本車"
drive関数が実行される時、関数が属するオブジェクトはcarなのでthisにはcarオブジェクトへの参照があります。
なので、this.name
は"日本車"となります。
スコープチェーン
callオブジェクトについて理解できればスコープチェーンも理解できます。
例として以下のロジックを利用します。
// トップレベルコード内
var text1 = "A";
function hoge() {
var text2 = "B";
function hogehoge() {
var text3 = "C";
console.log(text1 + text2 + text3);
}
hogehoge();
}
hoge(); // ABC
関数hogehogeは変数text1とtext2を宣言していませんが、どのように名前解決しているか説明します。
console.log(text1 + text2 + text3);
では以下のように名前解決しています。
- hogehoge関数のスコープ内でtext1変数を探す。(callオブジェクトのプロパティを探すけど見つからない)
- hogehoge関数のcallオブジェクトのプロパティに格納されている呼び出し元のcallオブジェクト(hoge関数のcallオブジェクト)を参照する。
- hoge関数のcallオブジェクトのプロパティにローカル変数text1がないか探す。見つからない。
- hoge関数のcallオブジェクトのプロパティに格納されている呼び出し元のcallオブジェクト(グローバルオブジェクト)を参照する。
- グローバルオブジェクトのプロパティに変数text1が存在するので、それを利用する。
- text2以降は同じことを繰り返す。
グローバルオブジェクトまで遡って見つからない場合はエラーとなります。
このように現在のスコープにない識別子は呼び出し元の変数オブジェクトを参照して探しに行きます。
これがスコープチェーンです。
クロージャ
クロージャ = 関数オブジェクト + 関数オブジェクトの参照する変数オブジェクト
よくある以下の例を元に説明します。
function makeCounter() {
var count = 0;
return f;
function f() {
return count++;
}
}
var a = makeCounter();
console.log(a()); // 0
console.log(a()); // 1
console.log(a()); // 2
makeCounter()は入れ子の関数f()を戻り値として返しています。
だから、aというグローバル変数にはmakeCounter()の入れ子であるf()関数の関数オブジェクトへの参照が格納される。
入れ子の関数f()は上位のmakecounterへの参照があります。
これでグローバル変数aからmakeCounterのcallオブジェクトへの参照があることになります。
グローバル変数のaから参照されている限り、makeCounterのcallオブジェクトはメモリに保持され続ける。
(makeCounter関数の処理が終了してもガベージコレクションされない)
だからa()
ごとにf()関数が実行され、countという変数はローカル変数として保持されているのでインクリメントされる。
countという変数はローカル変数なので外部からのアクセスはできない。(カプセル化)
makeCounter()で2つの関数を生成すれば、それぞれ独立したカウンタとなります。
なんでか説明できればクロージャを理解できていると思います。
てか、説明下手でわかりづらくてすみません。。。
コールバック関数
関数を関数の引数として渡すことができます。
渡した関数のことをコールバック関数と言います。
function test() {
console.log("コールバックされました。");
}
function main(callback, ..) {
callback();
}
main(test); // "コールバックされました。"
mainの引数として渡す関数testがコールバック関数です。
こうすると、呼び出したmainの中で任意のコールバック関数を呼び出すことができます。
よく使います。
最後に
ここまで関数について纏めましたが、まだまだ書いてないことはたくさんあります。
時間をとってもっと勉強したら内容を濃ゆく一つ一つ記事にできればと思います。
以上。