LoginSignup
76
89

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-05-17

JavaScriptの関数の基本事項

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

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

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

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

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

2.関数名(引数リスト)の形で呼び出される

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

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

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

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

第一級オブジェクトとは

Wikipediaによると以下のように説明されている。

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

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

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

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

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

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();

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

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の戻り値の関数を実行

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

property.js
function func() {};

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

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

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

スコープとは

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

JavaScriptのスコープについて

  • JavaScriptのスコープは{}で囲まれた範囲(ブロック)によって宣言されるのではなく、 関数によって宣言される。
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を用いた 変数宣言 がスコープに入るのは、それが宣言された場所から、その宣言を含む関数の終わりまでであるので 前方参照ができない。
  • 名前付き関数 がスコープに入るのは、その関数が宣言された関数全体であるので 前方参照が可能。この機構は 巻き上げ とも呼ばれる。
  • スコープ宣言に関して グローバルコンテクスト はページのコードを全て含む1個の巨大な関数のような役割を果たす。

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

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

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

  • 引数から値を受け取れなかったバラメータはundefinedとなり、関数内で使用される。
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.関数呼び出し時に暗黙のパラメータargumentsthisが渡される

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

argumentsについて

  • argumentsは、関数呼び出しの際に実際に渡された引数のコレクションである。
  • 配列のようにlengthプロパティを持ち、argument[index]の形で引数の値を参照できる。
  • ただし配列ではないので、配列のメソッドを使うことはできない。
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)となる。
call_as_function.js
function func(){
    if (this === window) {
        console.log('this = window');
    }
};

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

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

メソッドとしての呼び出し
  • オブジェクトのプロパティとなっている関数をそのオブジェクトの メソッドと呼ぶ。
  • つまり「メソッドとしての呼び出し」とは、オブジェクトのプロパティとなっている関数を呼び出すことである。
  • この時、thisの参照先は メソッドを所有するオブジェクトとなる。
  • 前述の「関数としての呼び出し」は「windowオブジェクトのメソッド」を呼び出していると考えるとわかりやすい(なのでthiswindowオブジェクトを指している)。
  • ただしグローバル関数でない場合も「関数としての呼び出し」ではthiswindowを指すので、あくまで 見かけ上「メソッドとしての呼び出し」の派生型に見えるという意味合いである(詳しくは Strictモードにおけるthisの注意点の項を参照)。
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が指したオブジェクトを戻り値として返却する。
  • 多くのプロパティが共通であるオブジェクトを、複数作る際によく使われる。
  • コンストラクタとして使用する関数の名前は 大文字から始まるのが通例である。
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引数以降に受け取る。
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演算子を使う。
typeof.js
var a = function(){};

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

とりあえず以上です。

参考書籍・サイト

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

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

補足

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

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

  • 関数func1を宣言
  • func1内部で変数aを宣言
  • func1内部で関数func2を宣言
  • func2内部で変数bを宣言
  • func2およびfunc1の外部で変数cを宣言
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. 
76
89
4

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
76
89