関数宣言式 - Function Declarations
一般的にプログラミング言語にて関数宣言と似たような形式です。
function 関数名() {
ロジック・・・
}
// 例
function funcDeclarations() {
return 'A function declaration';
}
funcDeclarations(); // 'A function declaration'
関数式 - Function Expressions
柔軟なJavascript言語の特徴を活用した宣言方式です。
var 関数名 = function () {
ロジック・・・
};
// 例
var funcExpression = function () {
return 'A function expression';
}
funcExpression(); // 'A function expression'
関数宣言式(Function Declarations)と関数式(Function Expressions)違い
関数宣言式はホイスティングを影響をますが、関数式はホイスティングの影響を受けていないです。
関数宣言式は、コードを実装した位置と関係なく、JavaScriptの特徴であるホイスティングによってブラウザがJavaScriptを解析する際に一番上に引き上げられる。
// 実行前
logMessage();
sumNumbers();
function logMessage() {
return 'worked';
}
var sumNumbers = function () {
return 10 + 20;
};
ホイスティングにより、JavaScript解析器はコードを下記のように認識する。
// 実行後
function logMessage() {
return 'worked';
}
var sumNumbers;
logMessage(); // 'worked'
sumNumbers(); // Uncaught TypeError: sumNumbers is not a function
sumNumbers = function () {
return 10 + 20;
};
上コードの結果
Uncaught TypeError: sumNumbers is not a function
関数式のsumNumbersにてvarもホイスティングに適用され、位置が上に引き上げられる。
var sumNumbers;
logMessage();
sumNumbers();
しかし、実際sumNumbersに割り当てられる関数ロジックは呼び出した以降に宣言されるので、sumNumbersは関数で認識されずに、変数として認識されます。
ホイスティングが正しくわからなくても、関数と変数をできるだけコードの上端部で宣言すれば、ホイスティングによるスコープのねじれ現象は防止できます。
関数式の長所
「関数式がホイスティングに影響されない」という特徴以外にも、関数宣言式より有効に使われる場合は以下の通りである。
・クロージャとして使用
・コールバックで使用(他の関数の印字に移行できる)
関数式でクロージャを生成する
クロージャは、関数を実行する前に当該関数に変数を渡したいときに使用されます。
理解を深めるため、以下の例題を見てみましょう。
function tabsHandler(index) {
return function tabClickEvent(event) {
// 外部関数tabsHandler()のindex印字をここでアクセス出来ます。
console.log(index);
};
}
var tabs = document.querySelectorAll('.tab');
var i;
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = tabsHandler(i);
}
上記の例は、すべての.tab 要素にクリックイベントを追加する例です。
注目すべき点は、クロージャを用いてtabClickEvent()で外関数tabsHandler()の印字値indexにアクセスした点であります。
function tabsHandler(index) {
return function tabClickEvent(event) {
console.log(index);
};
}
forループに合わせ実行が終わった後、使用者がtabをクリックした時、tabClickEvent()が実行されます。
もしクロージャを使わなかったらすべてのtabのindex値がforループの最後の値であるtabs.lengthの通りであります。
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = tabsHandler(i);
}
クロージャを使わなかった場合の例は下記をご参照ください。
var tabs = document.querySelectorAll('.tab');
var i;
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = function (event) {
console.log(i);
};
}
上記のソースはタブが3つであるとしたとき、どのタブをクリックしてもiはforループの最終値である3が示される。
問題点をより把握しやすくforループ中のfunction()を外に持ち出して宣言してみると
var tabs = document.querySelectorAll('.tab');
var i;
var logIndex = function (event) {
console.log(i); // 3
};
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = logIndex;
}
logIndexが実行される時点は、すでにforループの実行がすべて終わった時点です。
したがって、どのタブを押してもfor文の最終値である3 が表示されます。
この問題点を解決するためにクロージャを適用すると
function tabsHandler(index) {
return function tabClickEvent(event) {
console.log(index);
};
}
var tabs = document.querySelectorAll('.tab');
var i;
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = tabsHandler(i);
}
forループが実行されるとき、各i値をtabsHandler() に渡し、クロージャであるtabClickEvent()からtabsHandler()の印字値indexにアクセスできるようになります。
関数式を他の関数の印字で渡す
関数式一般的に臨時変数に格納するために使用します。
// doSthという臨時変数を使用する。
var doSth = function () {
// ...
};
関数式を臨時変数に格納してなくても、下記の様にコールバック関数として使用出来ます。
$(document).ready(function () {
console.log('An anonymous function'); // 'An anonymous function'
});
jQueryを使用してよく見られる文法で上記と下記の結果は同じです。
var logMessage = function () {
console.log('An anonymous function');
};
$(document).ready(logMessage); // 'An anonymous function'
Javascriptで基本的に提供するforEach()を使用する際にも、コールバックを使用出来ます。
var arr = ["a", "b", "c"];
arr.forEach(function (element) {
console.log(element); // a b c
});
コールバック関数を簡単に説明すると、関数の印字に渡す関数をコールバック関数と言います。
結論
関数式を関数宣言式と比べるとメリットは多いですが、結局はこのような違いを認知した状態で、一貫したコーディングコンベンションでコードを作成することが重要だと思います。
AirBnbのJS Styleガイドでも、関数宣言式よりは関数式を目指しています。
それでも、自分がコーディングしやすい方式で 実現するのがいいんじゃないでしょうか。