関数の「どこに書くか」を理解しよう(初心者向け)
短く言うと:「どこに関数を定義するか」と「どこから呼び出すか」は結果に大きく影響します。
ここではわかりやすい例をたくさん使って、間違いやすいポイントと実際に試せるコードを交えて説明します。
1. 基本 — 関数の書き方(おさらい)
まずは関数の書き方2種類を確認します。
関数宣言(function宣言/Function Declaration)
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
JavaScript関数式(Function Expression) — 変数に入れる
const add = function(a, b) {
return a + b;
};
console.log(add(2, 3)); // 5
JavaScript※ ES6以降はアロー関数も使えますが、ここでは挙動の違いに注目するため上の2つを押さえます。
2. 重要ポイント:*「巻き上げ(ホイスティング)」とその影響
JavaScriptでは「関数宣言」は巻き上げ(hoisting)されます。つまり、ファイルの上で宣言されたように扱われ、宣言より前に呼んでも動くことがあります。一方、関数式は巻き上げられない(変数の振る舞いに依存する)ので、定義より前に呼ぶとエラーになります。
関数宣言(呼び出しが先でもOK)
console.log(subtract(10, 4)); // 6 — 動く!
function subtract(x, y) {
return x - y;
}
JavaScriptなぜ動く?function subtract... の宣言部分は実行前に読み込まれ、subtractという名前が使える状態になっているからです。
関数式(定義より前に呼ぶとエラー)
console.log(multiply(2, 3)); // TypeError または ReferenceError
const multiply = function(x, y) {
return x * y;
};
JavaScriptmultiply は const に代入される「値」なので、代入(定義)が終わるまでは使えません。
3. HTMLの中で <script> を分けたときの注意
ブラウザで <script> を複数書く場合、上から下へ順に実行されます。だから「呼び出しが上、定義が下」だとエラーになります(関数宣言でも、別の <script> に分かれているときはその順で実行されるため注意が必要)。
NG(エラーになる可能性)
<!-- first script: ここで呼んでいる -->
<script>
sayHi(); // エラーになる(後のscriptで定義されていれば)
</script>
<!-- second script: 関数を定義 -->
<script>
function sayHi() {
console.log('Hi!');
}
</script>
JavaScriptOK にする方法
- 関数定義を呼び出しより上に置く
- あるいは、HTMLの
<body>下部にまとめて<script>を置く(ページが読み込まれてから実行される) deferやDOMContentLoadedイベントを使う(少し上級)
4. 関数の中で別の関数を定義した場合(ネスト) — スコープの話
関数の内部で定義した関数は、その外から直接呼べません(外側のスコープからは見えない)。これは「スコープ(有効範囲)」の基本です。
function outer() {
function inner() {
console.log('inner!');
}
inner(); // OK(outerの中からは呼べる)
}
outer(); // inner! が出る
inner(); // ReferenceError(outerの外からは見えない)
JavaScript使いどころ
- 専用のヘルパー関数をその外部に公開せずに使いたい時
- クロージャ(内側の関数が外側の変数を覚えている)を作るとき
5. 実例で理解しよう — 小さな例題3つ
例題A:関数宣言のホイスティング
console.log(greet('Aiko')); // "Hello, Aiko"
function greet(name) {
return 'Hello, ' + name;
}
JavaScript説明:greetは関数宣言なので、呼び出しが先でも動く。
例題B:関数式でのエラー例と修正
// NG
console.log(square(4)); // TypeError or ReferenceError
const square = function(n) {
return n * n;
};
// 修正(呼び出しを後に)
const square = function(n) { return n * n; };
console.log(square(4)); // 16
JavaScript説明:関数式は定義後に呼ぶ必要がある。
例題C:HTMLでの実用例(安全な書き方)
<!doctype html>
<html>
<body>
<button id="btn">押してね</button>
<script>
function setup() {
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
showMessage();
});
}
function showMessage() {
alert('ボタンが押されました!');
}
// ページ読み込み後に初期化
window.addEventListener('DOMContentLoaded', setup);
</script>
</body>
</html>
JavaScript説明:DOMContentLoaded を使えば、HTML要素が用意されるまで待ってから処理するので安全。
6. よくある間違い&対処法(初心者が陥りやすい)
- 間違い:関数式を定義する前に呼んでしまう → 解決:定義を先に、もしくは関数宣言を使う
- 間違い:
<script>を複数に分けて順序を間違える → 解決:関数を呼ぶ<script>の前に定義を置くか、defer/DOMContentLoadedを使う - 間違い:内部関数を外から呼ぼうとする → 解決:必要なら内部関数を戻り値や外側に出す設計にする(公開する関数を作る)
7. 練習問題
- 次のコードを実行して何が出るか予想してからブラウザで試してみてください。
console.log(foo()); // ?
function foo() { return 'foo'; }
JavaScript- 次のコードは動く? なぜ?どう直す?
console.log(bar());
const bar = () => 'bar';
JavaScript- 関数
parentの中にchildを定義して、parentの外からchildを呼び出す方法を考えてみてください(答えは下にあります)。
回答例(3の一つ)
function parent() {
function child() { return 'hello'; }
return child; // child を返す
}
const childRef = parent();
console.log(childRef()); // 'hello'
JavaScript8. ちょっと実務的なヒント(覚えておくと便利)
- ライブラリやモジュールを使うとき、どのファイルで関数を定義しているか、そのファイルが先に読み込まれるか は非常に重要です。
- モジュール(ES Modules)を使う場合は
import/exportを利用すると依存関係が明確になり、読み込み順の混乱が減ります。 - グローバル汚染(グローバル変数やグローバル関数を無闇に増やす)は後でバグの温床になるので避ける。関数はなるべくモジュールやオブジェクトにまとめる。
まとめ
- 関数宣言はホイスティングで「先に呼べる」ことがある。
- 関数式は定義後に呼ぶ必要がある。
- HTML内で
<script>を分けると実行順に注意。 - 関数のスコープ(有効範囲)を理解すると、安全でバグの少ないコードが書ける。
