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 はメモリ効率で有利とあります。