JavaScript

JavaScript の関数に関するあれこれ

JavaScript シリーズの最後は、JavaScript の関数に関するまとめ。自分の知らないことを中心にいくつかサンプルを書いて見ます。

関数の順番

JavaScript では、関数の定義は、関数を使用する前でも、後でも構いません。実行される時に、関数は全て前に持ってこられます。

arguments

JavaScript には予約されているarguments というプロパティがあります。それを使うと、可変のプロパティも扱えます。

function printArgs() {
    for (i = 0; i < arguments.length; i++) {
        console.log(arguments[i])
    }
}
x = printArgs(1, true, "Hello")

実行結果

1
true
Hello

関数の引数

関数の引数には、値渡しと、参照渡しがあります。プリミティブ型は値渡しで、オブジェクトだと参照渡しとのことです。下記の例では、statement は、文字列型、empはオブジェクトです。
changeValues 内で、パラメータに対して新しい値をアサインしていますが、実際に反映されるのは、オブジェクト (emp) のみです。

function changeValues(statement, emp) {
    statement = "Hi, guys";
    emp.name = "Taro";
}

var statement = "Hello guys";
var emp = new Employee("Yamada");

console.log(statement);
console.log(emp.name);
changeValues(statement, emp);
console.log(statement);
console.log(emp.name);

実行結果

Hello guys
Yamada
Hello guys
Taro

call

メソッドを呼び出す方法はいくつかありますが、その一つが、call を使う方法です。call を使うと、他のオブジェクトに属するメソッドを使えます。下記の例では、emp のメソッドを、myEmp の属性に対して実行しています。

var emp = {
    name: "Tsuyoshi",
    baseSalary: 100,
    salary: function() {
        return this.baseSalary * 2 + 1;
    }
}

var myEmp = {
    name: "Takeru",
    baseSalary: 200
}

console.log(emp.name);
console.log(emp.salary());
console.log(emp.salary.call(myEmp));
Tsuyoshi
201
401

apply

call とほぼ同じですが、引数を配列で扱えます。

console.log(Math.max.apply(null, [4,6,90]))
console.log(Math.max.call(null, 39,20,1))
90
39

self invoke method

関数は通常定義するだけでは、実行されませんが、定義して、即実行したい時に使います。

var value = "default";

// self invoke method

(function () {
    value = "hello!";
})();

console.log(value);

(function() {...})(); とすることにより、その場で関数が実行されるので、value の値が書き換えられます。実行結果はこちら。

hello!

closure

クロージャを使うと、関数に、通常 JavaScript ではできない、プライベートのプロパティや、関数を持つことができます。ポイントは、戻り値として関数を返しているところです。素直に考えると、関数を返した時点では、演算は行われず、関数を実行した時点での、counter が参照されそうです。ところが、クロージャでは、この関数が戻された時の、状態を保持したまま返されます。

だから、次のようなコードを書くと、add1 add2 それぞれで、値がインクリメントされます。

var add1 = (function () {
    var counter = 0;
    return function() {return counter += 1;}
})();

var add2 = (function () {
    var counter = 0;
    return function() {return counter += 1;}
})();

console.log(add1());
console.log(add1());
console.log(add2());
console.log(add2());

実行結果

1
2
1
2

通常の関数と、クロージャの違い

通常の関数と比べると、クロージャは関数を返しています。通常の関数だと、毎回countが初期化されるため、値を保持できません。

unction execFunc() {
    var count = 0;
    function add() {
        count++;
    }
    function display() {
        console.log(count);
    }
    add();
    display();
}

execFunc();
execFunc();

function execClosure() {
    var count = 0;
    return {
        add: function() {
            count++;
        },
        display: function() {
            console.log(count);
        }
    }
};

var closure = execClosure();
closure.add();
closure.display();
closure.add();
closure.display();

実行結果

1
1
1
2

Private の実現

この仕組みを利用して、プライベート属性を実現することができます。クロージャが戻されると、元々の関数の方には、クロージャからアクセスできますが、直接はアクセスできません。

var count = (function() {
    var counter = 0;
    function modify(num) {
        counter += num;
    }

    return {
        add: function() {
            modify(1);
        },
        remove: function() {
            modify(-1);
        },
        get: function() {
            return counter;
        }
    };
})();

console.log(count.get());
count.add();
count.add();
console.log(count.get());
count.remove();
console.log(count.get());

count.modify(1); // error

実行結果

0
2
1

 count.modify(1); // error
       ^

TypeError: count.modify is not a function

クロージャとクラス

ちなみに、状態を持てるということは、クラスと何が違うの?と思ってしまいます。

クロージャとクラスを比較すると、クロージャーの方がメモリをよく食う(メソッドが毎回インスタンス化されるので)しかし、テスタビリティは優れているという話もあります。(まだ調べていません。メモ程度)。となると、後のメリットである、Private を作れるという話ですが、ここまでやって、private を作るべきか?というと微妙かもと思います。効率を考えると基本クラスで問題なさげですが、クロージャの良いユースケースがあったらぜひ教えてください。

次のリソースの中には、DI を使っているときはクロージャはしやすいという記事がありました。ちなみに、使っていないときは、Class の方がモックが楽で、Clousure を使っているとできるけどダークマジックが必要とあります。clousure は liablity (責任を持つこと)に有利で、class はメモリ効率で有利とあります。

リソース