おはようございます。 関数や変数 今回は JavaScript のを宣言した際の動きについてまとめていきます。
関数の宣言
JavaScript での関数宣言は、function キーワードを使った方法と関数式を使用したものがあります。 それぞれどのような違いがあるのか見ていきましょう。
関数式
関数式は、関数を値として変数に代入する方法です。関数を値と言われるとイメージしにくいですが、JavaScript で関数は関数オブジェクトとも呼ばれオブジェクトの一種として扱われています。オブジェクトとは詳細は割愛しますが、ざっくり言うとデータの集合したものとイメージしてください。
つまり、関数もオブジェクトというデータの塊として扱われるので、文字列や配列と同様に変数に格納することが出来ます。(まだちょっとイメージしづらいと思いますが、ここではそうなんだ〜くらいに思って下さい。。。)
通常の関数宣言と、関数式の定義方法が以下のコードになります。
// functionキーワードを使った宣言
function testFunc1() {
console.log('hello world');
}
// 実行
testFunc1(); // hello world
// 関数式で宣言
const testFunc2 = function () {
console.log('hello world second');
};
// 実行
testFunc2(); // hello world second
関数の宣言方法が違うだけで実行する際は丸括弧()を付けて実行しているので一見同じように見えますが、以下のようにコードを書き換えると違いが分かります。
testFunc1(); // hello world
// 関数を実行している後に宣言
function testFunc1() {
console.log('hello world');
}
testFunc2(); // Cannot access 'testFunc2' before initialization
// 関数を実行している後に宣言
const testFunc2 = function () {
console.log('hello world second');
};
通常プログラムは上から順番に実行されますので、関数を実行した後に関数を宣言すると「そんな関数無いよ」というエラーが発生します。
関数式の方は「testFunc2 という関数が初期化されていないのでアクセスできない」とエラーが発生しています。
しかし、関数宣言の方は通常通り出力されています。
この動作に関して見ていきましょう。
ホイスティング
JavaScript ではコードを実行する前にホイスティング(宣言の巻き上げ) が行われます。ホイスティングとは、コード実行前に関数や変数をメモリに配置することを言います。
実行する前に定義した関数が準備されているので、先程のコードで見たように関数の実行後に関数宣言を行っていてもエラーになりません。
しかし、関数式で宣言した関数は代入処理が行われた時点で定義されるのでホイスティングが行われません。
testFunc1(); // エラー
let testFunc1 = function () {
console.log('hello world');
};
testFunc2(); // エラー
const testFunc2 = function () {
console.log('hello world second');
};
testFunc3(); // エラー
var testFunc3 = function () {
console.log('testFunc3');
};
まだこのくらいのコード量でしたらエラーが発生しても直ぐ気づけますが、これが何千とあるコードだとホイスティングが行われている関数が散らばっているとエラーの解決にかなり時間がかかってしまいます。
関数式で宣言した方が動作が分かりやすいので特別な理由がない限り、関数式を利用するのが良いかも知れません。
変数の宣言
JavaScript で変数の宣言方法は、var・let・const の 3 つがありますがそれぞれ動作が異なります。 簡単に表にまとめてみました。
変数名 | 再宣言 | 再代入 | スコープ | 初期化 |
---|---|---|---|---|
let | × | ● | ブロック | × ( undefined ) |
const | × | × | ブロック | × |
var | ● | ● | 関数スコープ | undefined |
それぞれどのようなものなのか見ていきましょう
変数の再宣言
変数の再宣言とは、一度宣言した変数をもう一度同じ名前で宣言することです。
先程の表を見てみると var のみ再宣言が可能となっていますが実際にコードを実行して確認してみます。
// var の場合
var x = 'おはようございます';
var x = 'こんばんは';
console.log(x); // こんばんは
// letの場合
let y = 'おはようございます';
let y = 'こんばんは';
console.log(y); // SyntaxError: Identifier 'y' has already been declared
// constの場合
const z = 'おはようございます';
const z = 'こんばんは';
console.log(z); // SyntaxError: Identifier 'z' has already been declared
varで同じ変数で再宣言した場合は、値が上書きされているのが分かります。
反対に let・const で同じ変数名を再宣言した場合はエラーになっているのが分かります。
再代入
変数の再代入とは、一度値を入れた変数に再び値を代入することを言います。
こちらもコード実際にコードで見ていきましょう。
// varの場合
var x = 'おはようございます';
// 値の再代入
x = 'こんばんは';
console.log(x); // こんばんは
// letの場合
let y = 'おはようございます';
// 値の再代入
y = 'こんばんは';
console.log(y); // こんばんは
// constの場合
const z = 'おはようございます';
// 値の再代入
z = 'こんばんは';
console.log(z); // エラー
先程の表通り、var・let で宣言した変数のみ再代入が可能で const の場合はエラーになっているのが分かります。
初期化
この初期化というのは値を何も入れずに変数の宣言をした時にその挙動に違いが出ます。
var x;
console.log(x); // undefined
let y;
console.log(y); // undefined
const z;
console.log(z); // SyntaxError: Missing initializer in const declaration
変数を宣言した時に何も値を代入しなかった場合に、var と let は undefined がデフォルト値として設定されている事が分かります。
しかし、let はホイスティングによる初期化が行われないため、コードを書く場所に注意しなければいけません。
console.log(x); // undefined
var x;
console.log(y); // ReferenceError: y is not defined
let y;
スコープ
スコープに関しては以下の記事で解説していますが、関数や変数を参照することの出来る範囲のことです。
関連記事を入れる
var はブロックスコープを持つことが出来ないので、ブロック { } の外からの参照が可能となります。
// ブロックスコープ
{
var x = 'おはようございます';
let z = 'こんにちは';
const y = 'こんばんは';
}
console.log(x); // おはようございます
console.log(z); // ReferenceError: z is not defined
console.log(y); // ReferenceError: y is not defined
// 関数スコープ
function scopeFunc() {
var x = 'おはようございます';
let z = 'こんにちは';
const y = 'こんばんは';
console.log(x); // おはようございます
console.log(z); // こんにちは
console.log(y); // こんばんは
}
scopeFunc();
console.log(x); // ReferenceError: x is not defined
console.log(z); // ReferenceError: z is not defined
console.log(y); // ReferenceError: y is not defined
let や const はブロックスコープを持つので、スコープ外からのアクセスを制限している事が分かります。
関数スコープは関数のブロック { } ごとに作られるスコープで、それぞれの変数は関数スコープ外からのアクセスを制限していることが分かります。
まとめ
関数や変数の動作について見てみましたが、JavaScript は他の言語と比べて自由に書くことが出来る!と言われている理由がわかったような気がします。
このような細かい言語仕様をしっかり 把握することで、エラーが発生したときの対応に差が出てくると思うので今回の記事が参考になれば幸いです。