WEBを勉強し始めて早くも1年が経ちました。
一年間WEBサイトの制作に関わってきて、JavaScriptもだいたいわかったつもりでいたのですが、どうやら基本すら全くわかっていないのではないか?と思い、基礎を一から学んでみることに。
第一回目は変数宣言について勉強したことについてのメモです。
変数宣言
変数宣言に使用できるキーワードは var, let, const の3種類があります。
varしかほとんど使ったことがなかったのですが、ちゃんと調べてみるとvarを多用することはただ 時代遅れか知識不足だと主張しているようなものだと言うことが分かりました。
変数宣言の基本
まず基本事項などを確認していきます。上ではvar使うなんて..と書いてますがとりあえず親しみやすいのでvarを使用してまとめておきます。
変数宣言してみる (初期値なし)
var hoge;
hoge という変数を宣言しました。初期値を代入していない場合、 この段階では「 undefined」として初期化されます。
変数宣言してみる (初期値あり)
var hoge = "ほげ〜";
このようにして宣言時に初期値を設定できる。
変数名に使用できるもの
上記の例では「hoge」という名前で変数を宣言していますが、この文字列にどのようなものが使えるかもまとめてみます。
//以下はOK
var hogeHoge;
var $hoge;
var _hoge;
var hoge2;
var hogeほげ;
//以下はNG
var 2hoge;
var ほげ;
var var; //予約語
まとめると、
- 1文字目は「a-z」「A-Z」のアルファベットに加え、「 $」または「 _」でもOK。
- 2文字目以降は 数字や 日本語もOK。
- ただし、 予約語はNG。 (予約語については こちらで詳しく紹介されています。)
var・let・constの比較
var・let・constの違いを見ていきます。
有効範囲(スコープ)について
varが 関数スコープなのに対し、letとconstは ブロックスコープが有効になります。
ブロックというのは { } で囲まれた範囲のことです。
varの有効範囲
function(){
var hoge = "ほげ";
if(hoge === "ほげ"){
var hoooge = "ほーーーげ";
}
for(var i; i < 10; i++){
//処理
}
console.log(hoge); //ほげ
console.log(hoooge); //ほーーーげ
console.log(i); //10
}
console.log(hoge); //ReferenceError
letとconstの有効範囲
function(){
let hoge = "ほげ";
if(hoge === "ほげ"){
let hoooge = "ほーーーげ";
}
for(let i; i < 10; i++){
//処理
}
console.log(hoge); //ほげ
console.log(hoooge); //ReferenceError
console.log(i); //ReferenceError
}
console.log(hoge); //ReferenceError
再定義について
letとconstは 同一スコープ内で同じ変数名を再定義できません 。
//有効スコープ内で
var hoge;
var hoge; //OK
let hoge2;
{
let hoge2; //ここは新しいブロックスコープ内なのでOK
}
let hoge2; //SyntaxError
再代入について
varとletは再代入が可能ですが、 constは再代入ができません。
var hoge;
hoge = "ほげ";
hoge = "ほげほげ"; //エラーなし
//再代入できない
const hoge ="ほげ";
hoge = "ほげほげ"; //TypeError: invalid assignment to const `hoge'
//初期値がない場合にもエラーとなる
const hoge; //SyntaxError: missing = in const declaration
宣言によるホイスト(巻き上げ)の違い
JavaScriptで宣言されたローカル変数は、すべてそのスコープの先頭で宣言されたものとみなされます。
このことを変数の巻き上げというそうですが、この巻き上げに関しても、varとlet・constでは違いが見られます。
varの巻き上げ
さっそくですが、例えば以下のコードの実行結果はどうなるでしょうか。
var hoge = "ほげほげ";
(function myfunc() {
console.log(hoge); //1回目の出力
var hoge = "ほげ~";
console.log(hoge); //2回目の出力
})();
// 出力結果
// 1 -> undefined
// 2 -> "ほげ~"
グローバルレベルで変数hogeを宣言しており、続いてmyfunc()という即時関数でhogeを出力、その後にもう一度hogeを再定義してから再出力しています。
普通に考えれば、一回目の出力では最初に定義された "ほげほげ" が出力され、2回目で新しく定義された "ほげ~" が出力されそうです。
しかし、出力を見ると1回目の出力は undefined となります。
「JavaScriptで宣言されたローカル変数は、すべてそのスコープの先頭で宣言されたものとみなされる」という性質により、実際は以下のように動作しているからです。
var hoge = "ほげほげ";
(function myfunc() {
var hoge; //myfunc()のスコープの先頭で宣言されたものとみなされる
console.log(hoge); //1回目の出力
hoge = "ほげ~";
console.log(hoge); //2回目の出力
})();
つまり、myfunc() の先頭でhogeという変数は 初期値なしで定義だけされた状態になっていたのです。
それ故、1回目の出力ではundefinedが出力されてしまうようです。
let・constの巻き上げ
varでは、巻き上げが起こるとundefinedが返されることがわかりました。しかし、let,constではundefinedではなく、 ReferenceErrorを返してきます。
let hoge = "ほげほげ";
(function myfunc() {
console.log(hoge); // ReferenceError: hoge is not defined
let hoge = "ほげ~";
})();
varと同様のことが起こっているなら、letも下位スコープの中での再定義は可能なので、undefinedが返されそうですが、 巻き上げが起こっていればエラーを返す、という仕様になっているようです。
letを見るとそもそも巻き上げが発生しない、と書かれていることは多いですが、上記のとおり、巻き上げは発生しています。(巻き上げが発生していないなら、"ほげほげ"が出力されるはずです。)
また、上記のコードをconstに変えても同様にReferenceErrorが発生するのですが、このことからも巻き上げが発生していることがわかります。(SyntaxErrorではなく、ReferenceErrorとなるので。)
const hoge = "ほげほげ";
(function myfunc() {
console.log(hoge); // ReferenceError: hoge is not defined となる。
const hoge = "ほげ~"; // 巻き上げが発生していない場合、ここでSyntaxError: Identifier 'hoge' has already been declaredが返されるはず
})();
let・constを使用する際の注意点
(2017/10/07 に追記しました。)
ここまで、let・constを使おう!ということを書いてきたのですが、注意すべき点があります。
ES2015(ES6)から追加された仕様であること
つまり、ES6に対応していないブラウザでは動かず、エラーになります。
例えば、 IEは11からでないと対応していません。
ですが、let・constの他にも優れた仕様が追加されており、今のうちからES6に慣れていくことが重要です。
コードはES6に対応した記述をしておき、実際には「 Babel」などのツールで ES5にトランスパイルさせたものをサーバーに置いておく、というのが流行っているそうです。
個人的にはIE10なども対応しなければならない場合は諦めて今まで通りES5で記述をして、モダンブラウザだけに対応すればいい案件や個人の運営サイトがあればそこで思う存分練習すればいいかな、と思っています。
厳格モード( use strict )を忘れずに
ES6の仕様的には、let・constともに strictモード(厳格モード)でも sloppyモード(非strictモード)でも同じ動作をするようですが、ブラウザの実装の問題上でsloppyモードではvarで宣言した場合と同じような挙動を示す場合があるようです。
'use strict' の記述を忘れてしまっていても大きな問題にはならないと思いますが、最近はstrictモードで記述するのが常識になっているようですので、この機会にstrictモードに慣れていくといいかもしれません。
letとconstはグローバルオブジェクトのプロパティにならない
グローバルスコープで var で宣言した変数はグローバル変数としてグローバルオブジェクトのプロパティに登録されます。
グローバルオブジェクトとは、ブラウザであればwindow、nodejsであればglobalといった名前で定義されており、定義されたグローバル変数を自身のプロパティとして持つという特徴があります。
例えば、windowオブジェクトのプロパティとしてアクセスしてみると、
"use strict";
console.log(window.hoge); // 出力 -> "ほげ"
console.log(typeof window.hoge); // 出力 -> 'string'
このように、windowオブジェクトのhogeというプロパティとして定義した変数の内容、"ほげ" が登録されている。
しかし、let,constで試してみると、
"use strict";
let hoge = "ほげ";
console.log(window.hoge); // 出力 -> undefined
console.log(typeof window.hoge); // 出力 -> 'undefined'
const hoge2 = "ほげ";
console.log(window.hoge2); // 出力 -> undefined
console.log(typeof window.hoge2); // 出力 -> 'undefined'
という結果に。グローバルオブジェクトのプロパティには登録されないらしい。
参考文献
ES6(ES2015)でグローバルに定義したlet,const,classはグローバルオブジェクトのプロパティにならない
おわりに
var しか使っていなかったのですが、 let や const の違いをしっかり頭に入れた上でこれらを活用できるとより良いコードが書けそうです。
変数宣言だけでこんなにもまとめるべきことがあるとは、JavaScriptは奥が深いです...。