JavaScriptの関数の基本事項をまとめる会

# JavaScriptの関数の基本事項

備忘＆復習も兼ねて、JavaScriptの関数の基本的な使い方や特徴をまとめてみたいと思います。
(途中で確認のためのコードを挟んでいますが、出力結果は省略してあります)

## 1.リテラルを介して作成される

### リテラルの4つの構成要素

JavaScriptの関数は以下の4つの要素で構成されたリテラルによって作成される(**関数宣言**)。 

1. 予約語 `function`
1. `関数名`(省略可能)
	* JavaScriptの識別子として有効な名前である必要がある
	* 関数名をつけておくと、外部から呼び出すことができるだけでなく、内部で再帰的に自身を呼び出すことも可能になる
	* 関数名が省略された場合は **無名関数** とよばれる 
1. `()` で囲まれたカンマ区切りの関数のパラメータのリスト
	* リストは空でもよいが、`()`は必ずつける
1. `{}` で囲まれた命令文
	* 空でもよいが、`{}`は必ずつける

```js:function_literal.js
function sample(arg1, arg2) {
	console.log('arg1 = ' + arg1 + ', arg2= ' + arg2);
}
```

## 2.`関数名(引数リスト)`の形で呼び出される
`関数名(引数リスト)`の形で関数を呼び出すことができる。
(命令文の実行および戻り値の取得)

```js:function_call.js
function sample2(arg) {
	return 'arg = ' + arg;
}
var result = sample2('test');
```

## 3.第一級オブジェクトである

JavaScriptにおいて、関数は第一級(ファーストクラス)オブジェクトである。

### 第一級オブジェクトとは

[Wikipedia](http://ja.wikipedia.org/wiki/%E7%AC%AC%E4%B8%80%E7%B4%9A%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88 "第一級オブジェクト")によると以下のように説明されている。

```
第一級オブジェクト（ファーストクラスオブジェクト、first-class object）は、あるプログラミング言語において、たとえば生成、代入、演算、（引数・戻り値としての）受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。
```

よって、例えば以下に挙げた例のように、他のJavaScriptオブジェクトと同様に扱うことができる。

**変数に代入することができる**

```js:variable.js
var variable = function() {
	console.log('variable');
}
variable();
```

**配列のエントリやオブジェクトのプロパティに代入することができる**

```js:array_and_object.js
var array = [
	function(){console.log('array[0]');},
	function(){console.log('array[1]');},
	function(){console.log('array[2]');}
];
for (var i = 0; i < array.length; i++) {
	array[i]();
}

var obj = {
	func1:function(){console.log('func1');},
	func2:function(){console.log('func2');}
};
obj.func1();
obj.func2();
```

**他の関数の引数や戻り値として扱うことができる**

```js:function_args_and_reture_value.js
var arg_function = function() {
	return 'execute arg_function';
}

var ret_function = function(func) {
	var result = func();	//引数で受けた関数を実行して戻り値を変数に格納
	
	//関数を戻り値として返す
	return function() {
		console.log(result);
		console.log('execute ret_function');
	};
}

var func = ret_function(arg_function);
func();	//ret_functionの戻り値の関数を実行
```

**動的に作成・代入が可能なプロパティを持つことができる**

```js:property.js
function func() {};

func.a = 'aaa';
func.b = function(){console.log('bbb');};

console.log(func.a);
func.b();
```

## 4.スコープ宣言の単位である

### スコープとは

* 変数の有効範囲のことで、プログラムのどの場所から変数を参照できるかを決定する概念

### JavaScriptのスコープについて

* JavaScriptのスコープは`{}`で囲まれた範囲(**ブロック**)によって宣言されるのではなく、 **関数によって宣言される。**

```js:scope_range.js
if (true) {
	var x = 1;	//ifブロックの内部で宣言
}
console.log(x);	//xはブロックの外側からも参照できる

function create_scope() {
	var y = 2;	//関数内部で宣言
	console.log('inner y = ' + y);	//関数内部からはもちろん参照できる
}
create_scope();
console.log('outer y = ' + y);	//スコープ外なので、関数の外側からは参照できないのでエラー
```

## 5.宣言されたスコープ内での前方参照が可能である

### 変数宣言および名前付き関数のスコープについて

* `var`を用いた **変数宣言** がスコープに入るのは、それが宣言された場所から、その宣言を含む関数の終わりまでであるので **前方参照ができない。**
* **名前付き関数** がスコープに入るのは、その関数が宣言された関数全体であるので **前方参照が可能。**この機構は **巻き上げ** とも呼ばれる。
* スコープ宣言に関して **グローバルコンテクスト** はページのコードを全て含む１個の巨大な関数のような役割を果たす。

(確認用のコードおよび出力結果は冗長のため記事末尾に記載します)

## 6.宣言時のパラメータと関数呼び出し時の引数の数が異なっていてもよい

### 宣言時のパラメータ数が関数呼び出し時の引数よりも多い場合

* 引数から値を受け取れなかったバラメータは`undefined`となり、関数内で使用される。

```js:params_count.js
function undefined_check(arg) {
	if (typeof(arg) != 'undefined') {
		console.log('arg = ' + arg);
	} else {
		console.log('undefined!!!');
	}
}

undefined_check('a');	//引数を渡した場合
undefined_check();	//引数を渡さなかった場合
```

### 宣言時のパラメータ数が関数呼び出し時の引数よりも少ない場合

* 多く渡された分の引数の値は宣言時のパラメータ名では参照できない。
* しかし暗黙のパラメータ`arguments`を介して参照することができる(`arguments`については次節を参照)

## 7.関数呼び出し時に暗黙のパラメータ`arguments`と`this`が渡される

関数を呼び出すと、暗黙のパラメータとして`arguments`と`this`が渡される。
両者はともに関数内で変数として扱うことができる。

### `arguments`について

* `arguments`は、関数呼び出しの際に実際に渡された引数のコレクションである。
* 配列のように`length`プロパティを持ち、`argument[index]`の形で引数の値を参照できる。
* ただし配列ではないので、配列のメソッドを使うことはできない。

```js:arguments.js
function arguments_check() {
	if (arguments.length > 0) {
		for (var i = 0; i < arguments.length; i++) {
			console.log('arguments[' + i + '] = ' + arguments[i]);
		}
	}
}

arguments_check(0, 'a', 'b');
```

### `this`について

* `this`はその関数呼び出しに暗黙的に関連付けられる **オブジェクト**を参照する(**関数コンテクスト**とも呼ばれる)。
* `this`がどのようなオブジェクトを示すかは、その関数が **どのように呼び出されたか**によって決まる。

### 関数呼び出しの分類と`this`の中身について

関数呼び出しは以下の4パターンに分類される。

* **関数**としての呼び出し
* **メソッド**としての呼び出し
* **コンストラクタ**としての呼び出し
* `apply()`または`call()`メソッドによる呼び出し

これらの関数呼び出しの違いによって、`this`の中身も異なったものになる。

##### 関数としての呼び出し

* 関数を「関数として」呼び出す = 「メソッドとしてでも、コンストラクタとしてでも、`apply()`や`call()`を介してでもなく」呼び出すということを意味する。
* つまり、普通に`関数名(引数)`という形で呼び出した場合を指す。
* この時、`this`の参照先は **グローバルコンテクスト**(`window`)となる。

```js:call_as_function.js
function func(){
	if (this === window) {
		console.log('this = window');
	}
};

func();	//関数として呼び出し
```

ただし、 **Strictモード**でこの呼び出し方をすると、`this`の参照先は`undefined`となる。
(詳細は **Strictモードにおける`this`の注意点**の項を参照)

##### メソッドとしての呼び出し

* オブジェクトのプロパティとなっている関数をそのオブジェクトの **メソッド**と呼ぶ。
* つまり「メソッドとしての呼び出し」とは、オブジェクトのプロパティとなっている関数を呼び出すことである。
* この時、`this`の参照先は **メソッドを所有するオブジェクト**となる。
* 前述の「関数としての呼び出し」は「`window`オブジェクトのメソッド」を呼び出していると考えるとわかりやすい(なので`this`は`window`オブジェクトを指している)。
* ただしグローバル関数でない場合も「関数としての呼び出し」では`this`は`window`を指すので、あくまで **見かけ上「メソッドとしての呼び出し」の派生型に見える**という意味合いである(詳しくは **Strictモードにおける`this`の注意点**の項を参照)。

```js:method.js
function disp_property_with_this(){
	console.log('a = ' + this.a);
}
var obj = {
	a:1,
	method:disp_property_with_this
};

obj.method();	//メソッドとして呼び出し
```

##### コンストラクタとしての呼び出し

* `new`キーワードを付けて関数を呼び出すと、関数を **コンストラクタ**として呼び出すことができる
* コンストラクタとして関数を呼び出すと空のオブジェクトを自動的に生成し、そのオブジェクトが`this`の参照先となる。
* 呼び出した関数が明示的な戻り値を返却しない場合は`this`が指したオブジェクトを戻り値として返却する。
* 多くのプロパティが共通であるオブジェクトを、複数作る際によく使われる。
* コンストラクタとして使用する関数の名前は **大文字から始まる**のが通例である。

```js:constructor.js
function Sample(number) {
	this.objType = 'sample';
	this.objNumber = number;
}

//コンストラクタ呼び出し
var obj1 = new Sample(1);
console.log('Type : ' + obj1.objType + ', Number : ' + obj1.objNumber);

//別の引数でコンストラクタ呼び出し
var obj2 = new Sample(2);
console.log('Type : ' + obj2.objType + ', Number : ' + obj2.objNumber);
```

##### `apply()`または`call()`メソッドによる呼び出し

* 関数オブジェクトは全て`apply()`と`call()`メソッドを持つ
* `apply()`または`call()`を使って関数を実行する時、`this`の参照先は **第1引数として渡したオブジェクト**となる。
* つまり`this`が指すオブジェクトを動的に変更したい場合に用いられる関数呼び出しである。
* `apply()`は本来の関数に渡したい引数を1つにまとめた配列を第2引数として受け取る。
* `call()`は本来の関数に渡したい引数を第2引数以降に受け取る。

```js:apply_and_call.js
var target = 'global';

var obj1 = {target:'obj1'};
var obj2 = {target:'obj2'};

function disp_target_name(methodName) {
	console.log('method = ' + methodName);
	console.log('target = ' + this.target);
}

disp_target_name('self');	//関数として呼び出し
disp_target_name.apply(obj1, ['apply']);	//applyによる呼び出し
disp_target_name.call(obj2, 'call');	//callによる呼び出し
```

### Strictモードにおける`this`の注意点

ECMAScript5から導入された **Strictモード**においては、`this`が指すオブジェクトは上記の例とは異なってくるので注意が必要である。
Strictモードでない場合と比較すると以下のような相違点がある。

```
【非Strictモード】
・数値や真偽値等、プリミティブ値をthisの値として指定した場合、ラッパークラスに自動変換される
・undefinedやnullをthisの値として指定した場合、グローバルオブジェクト(window)に自動変換される

【Strictモード】
・thisとして指定された値の自動変換は行われない
```

この違いは「関数としての呼び出し」において特に重要である。

* 関数としての呼び出しにおいて、本来`this`の値は`undefined`と指定されている。
* 非Strictモードであれば`this`の参照先はグローバルオブジェクト`window`に自動変換される。
* しかしStrictモードでは`this`の参照先は`undefined`のままである。

## 8.その他

* JavaScriptでは`Function()`という組み込み関数が存在し、関数を宣言すると`Function()`コンストラクタによってオブジェクトが作成される。
* あるオブジェクトが関数であるかを判定するには`typeof`演算子を使う。

```js:typeof.js
var a = function(){};

console.log(typeof(a));	//'function'という値になる
```

***

とりあえず以上です。

# 参考書籍・サイト

以下の書籍を参考とさせていただきました。

* [JavaScript Ninjaの極意/Bear Bibeault, John Resig, 吉川邦夫](http://www.amazon.co.jp/dp/B00ESXY9MA)
* [プロになるためのJavaScript入門/河村 嘉之, 川尻 剛](http://www.amazon.co.jp/dp/4774154385)

また、以下のサイトを参考とさせていただきました。

* [MDN(Strictモードに関する記述)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Strict_mode#JavaScript_.E3.81.AE_.22.E3.82.BB.E3.82.AD.E3.83.A5.E3.82.A2.E5.8C.96.22)

# 補足

### 「5.宣言されたスコープ内での前方参照が可能である」確認用コードおよび出力結果

変数および関数は以下の順番で宣言する

* 関数func1を宣言
* func1内部で変数aを宣言
* func1内部で関数func2を宣言
* func2内部で変数bを宣言
* func2およびfunc1の外部で変数cを宣言

```js:scope_range2.js
console.log('|---コードの先頭---|');
console.log(typeof(a) == 'number' ? 'a is in scope.' : 'a is not in scope.');
console.log(typeof(b) == 'number' ? 'b is in scope.' : 'b is not in scope.');
console.log(typeof(c) == 'number' ? 'c is in scope.' : 'c is not in scope.');
console.log(typeof(func1) == 'function' ? 'func1 is in scope.' : 'func1 is not in scope.');
console.log(typeof(func2) == 'function' ? 'func2 is in scope.' : 'func2 is not in scope.');

function func1() {
    console.log('|---func1内部、a宣言前---|');
    console.log(typeof(a) == 'number' ? 'a is in scope.' : 'a is not in scope.');
    console.log(typeof(b) == 'number' ? 'b is in scope.' : 'b is not in scope.');
    console.log(typeof(c) == 'number' ? 'c is in scope.' : 'c is not in scope.');
    console.log(typeof(func1) == 'function' ? 'func1 is in scope.' : 'func1 is not in scope.');
    console.log(typeof(func2) == 'function' ? 'func2 is in scope.' : 'func2 is not in scope.');

    var a = 1;

    console.log('|---func1内部、a宣言後、func2宣言前---|');
    console.log(typeof(a) == 'number' ? 'a is in scope.' : 'a is not in scope.');
    console.log(typeof(b) == 'number' ? 'b is in scope.' : 'b is not in scope.');
    console.log(typeof(c) == 'number' ? 'c is in scope.' : 'c is not in scope.');
    console.log(typeof(func1) == 'function' ? 'func1 is in scope.' : 'func1 is not in scope.');
    console.log(typeof(func2) == 'function' ? 'func2 is in scope.' : 'func2 is not in scope.');

    function func2(){
        console.log('|---func2内部、b宣言前---|');
        console.log(typeof(a) == 'number' ? 'a is in scope.' : 'a is not in scope.');
        console.log(typeof(b) == 'number' ? 'b is in scope.' : 'b is not in scope.');
        console.log(typeof(c) == 'number' ? 'c is in scope.' : 'c is not in scope.');
        console.log(typeof(func1) == 'function' ? 'func1 is in scope.' : 'func1 is not in scope.');
        console.log(typeof(func2) == 'function' ? 'func2 is in scope.' : 'func2 is not in scope.');

        var b = 2;

        console.log('|---func2内部、b宣言後---|');
        console.log(typeof(a) == 'number' ? 'a is in scope.' : 'a is not in scope.');
        console.log(typeof(b) == 'number' ? 'b is in scope.' : 'b is not in scope.');
        console.log(typeof(c) == 'number' ? 'c is in scope.' : 'c is not in scope.');
        console.log(typeof(func1) == 'function' ? 'func1 is in scope.' : 'func1 is not in scope.');
        console.log(typeof(func2) == 'function' ? 'func2 is in scope.' : 'func2 is not in scope.');
    }
    
    //func2実行
    func2()
    
    console.log('|---func1内部、func2外部---|');
    console.log(typeof(a) == 'number' ? 'a is in scope.' : 'a is not in scope.');
    console.log(typeof(b) == 'number' ? 'b is in scope.' : 'b is not in scope.');
    console.log(typeof(c) == 'number' ? 'c is in scope.' : 'c is not in scope.');
    console.log(typeof(func1) == 'function' ? 'func1 is in scope.' : 'func1 is not in scope.');
    console.log(typeof(func2) == 'function' ? 'func2 is in scope.' : 'func2 is not in scope.');
}

//func1実行
func1();

console.log('|---func1外部、c宣言前---|');
console.log(typeof(a) == 'number' ? 'a is in scope.' : 'a is not in scope.');
console.log(typeof(b) == 'number' ? 'b is in scope.' : 'b is not in scope.');
console.log(typeof(c) == 'number' ? 'c is in scope.' : 'c is not in scope.');
console.log(typeof(func1) == 'function' ? 'func1 is in scope.' : 'func1 is not in scope.');
console.log(typeof(func2) == 'function' ? 'func2 is in scope.' : 'func2 is not in scope.');

var c = 3;

console.log('|---c宣言後---|');
console.log(typeof(a) == 'number' ? 'a is in scope.' : 'a is not in scope.');
console.log(typeof(b) == 'number' ? 'b is in scope.' : 'b is not in scope.');
console.log(typeof(c) == 'number' ? 'c is in scope.' : 'c is not in scope.');
console.log(typeof(func1) == 'function' ? 'func1 is in scope.' : 'func1 is not in scope.');
console.log(typeof(func2) == 'function' ? 'func2 is in scope.' : 'func2 is not in scope.');
```

**出力結果**

```
|---コードの先頭---|
a is not in scope.
b is not in scope.
c is not in scope.
func1 is in scope.
func2 is not in scope.
|---func1内部、a宣言前---|
a is not in scope.
b is not in scope.
c is not in scope.
func1 is in scope.
func2 is in scope.
|---func1内部、a宣言後、func2宣言前---|
a is in scope.
b is not in scope.
c is not in scope.
func1 is in scope.
func2 is in scope.
|---func2内部、b宣言前---|
a is in scope.
b is not in scope.
c is not in scope.
func1 is in scope.
func2 is in scope.
|---func2内部、b宣言後---|
a is in scope.
b is in scope.
c is not in scope.
func1 is in scope.
func2 is in scope.
|---func1内部、func2外部---|
a is in scope.
b is not in scope.
c is not in scope.
func1 is in scope.
func2 is in scope.
|---func1外部、c宣言前---|
a is not in scope.
b is not in scope.
c is not in scope.
func1 is in scope.
func2 is not in scope.
|---c宣言後---|
a is not in scope.
b is not in scope.
c is in scope.
func1 is in scope.
func2 is not in scope. 
```