30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

関数のTips

関数の定義方法

JavaScriptで関数を定義する方法は、関数宣言と関数式の2つ。
本当はもう一つあるが、それは非推奨のものなので、ここでは省略する。

// 関数宣言
function myFunc1(){};

// 関数式
var myFunc2 = function(){};

どちらでも同じように関数が作成される。
違いとしては、関数宣言では関数の巻き上げが行われるが、関数式では行われない。

関数の巻き上げ

関数の巻き上げとは、コードの実行前に関数を読み込むこと。
この動作により、定義前の関数を呼び出すことが出来る。

function showResult(f){
    console.log(f);
};

showResult(myFunc1(1,2));   // 3
showResult(myFunc2(1,2));   // エラー

// 関数宣言
function myFunc1(a, b){
    return a+b;
};

// 関数式
var myFunc2 = function(a, b){
    return a+b;
};

myFunc1myFunc2は同じ内容の関数だが、定義方法が違う。

関数宣言で定義したmyFunc1は、定義されるよりも前に呼び出しているが、問題なく実行できる。

しかし、関数式で定義したmyFunc2は、定義される前に呼び出すと、エラーになる。

Function.prototype

全ての関数は、Functionインスタンスである。
そのため、Function.prototypeのメソッドやプロパティにアクセスできる。

主なメソッドとしては、callapplyがある。
プロパティとしては、lengthargumentsがある。

function myFunc1(){};
var myFunc2 = function(){};

console.log(myFunc1 instanceof Function);    // true
console.log(myFunc2 instanceof Function);    // true

console.log(Function.prototype.isPrototypeOf(myFunc1));    // true

// プロトタイプとして例えば、call()やlengthがある
console.log(Function.prototype.hasOwnProperty('call'));   // true
console.log(Function.prototype.hasOwnProperty('length')); // true

callapplyについては下記を参照。
this, call(), apply(), bind()

lengthやarguments

どちらも、Function.prototypeが持つプロパティ。

length

lengthプロパティは、その関数に定義されているパラメータの数を返す。
実際に引数がいくつ与えられるかは、無関係。

function showLength(a, b, c){
    console.log(showLength.length);
};

// 全て3を返す
showLength();
showLength(1);
showLength(1, 2);
showLength(1, 2, 3, 4);

arguments

argumentsはオブジェクト。配列ではないが、配列のような使い方が出来る。
関数に渡された引数はargumentsに格納されていく。

function myFunc(){
    console.log(arguments[0], arguments[1]);
    console.log(arguments.length);   // これは、渡された引数の数を返す
};

myFunc(7);
// 7 undefined
// 1

myFunc('a', 'b');
// a b
// 2

Strictモードにおけるargumentsの挙動

Strictモードと標準モードでは、argumentsの挙動が異なる。

標準モードでは、パラメータとargumentsは、名前が異なるだけで、同一のものとして扱われる。

// 標準モード

function myFunc(val){
    val = 5;
    console.log(val, arguments[0]);
    arguments[0] = 9;
    console.log(val, arguments[0]);
};

myFunc(1);
// 5 5
// 9 9

だがStrictモードでは、両者は別のものとして扱われる。

'use strict'

function myFunc(val){
    val = 5;
    console.log(val, arguments[0]);
    arguments[0] = 9;
    console.log(val, arguments[0]);
};

myFunc(1);
// 5 1
// 5 9

但し、引数に参照型(オブジェクト)が与えられた場合は、パラメータもargumentsも同じオブジェクトを参照するため、実質的に同じものにアクセスすることになる。

'use strict'

function myFunc(obj){
    obj.prop = 1
    console.log(obj.prop, arguments[0].prop);
    arguments[0].prop = 9;
    console.log(obj.prop, arguments[0].prop);
};

myFunc({});
// 1 1
// 9 9

Strcitモードでは、argumentsに値を代入し直すことは、出来なくなる。
代入しようとすると、エラーになる。

// 標準モード

function normalFunc(val){
    console.log(arguments[0]);
    arguments = ['new arguments'];
    console.log(arguments[0]);
};

normalFunc(1);
// 1
// new arguments
'use strict'

// SyntaxError: Unexpected eval or arguments in strict mode
function strictFunc(val){
    console.log(arguments[0]);
    arguments = ['new arguments'];
    console.log(arguments[0]);
};

但し、要素の追加や変更は、Strictモードでも問題なく行える。

'use strict'

function myFunc(val){
    console.log(arguments[0], arguments[1]);
    arguments[1] = arguments[0] * 2;
    arguments[0] = 99;
    console.log(arguments[0], arguments[1]);
};

myFunc(5);
// 5 undefined
// 99 10

オブジェクトとしての関数

JavaScriptでは、プリミティブ型以外の全てのデータ型は、オブジェクトである。
関数も当然、オブジェクトである。
そのため、プロパティを与えることが出来るし、変数に代入した際は参照渡しとなる。

function myFunc(){};

console.log(myFunc instanceof Function); // true
console.log(myFunc instanceof Object);   // true

console.log(Function.prototype.isPrototypeOf(myFunc)); // true
console.log(Object.prototype.isPrototypeOf(Function.prototype));  // true

// プロパティを持たせることが出来る
myFunc.prop = 1;

var variable1 = myFunc;
var variable2 = myFunc;

console.log(variable1.prop);    // 1

// variable1とvariable2は同じものを参照しているため、
// 変更は両方に反映される。
variable2.prop = 99;
console.log(variable1.prop);    // 99
console.log(variable2.prop);    // 99

再帰

再帰とは、関数がその処理のなかで自分自身を呼び出すこと。

function myFunc(num){
    console.log(num);
    num--;
    if(!(num < 0)){ myFunc(num) };
};

myFunc(3);
// 3
// 2
// 1
// 0