JavaScriptの変数宣言について
こんにちは!皆さんは変数を宣言したことはありますか?
私はあります!
JavaScriptで変数を宣言するときって
- var
var foo
- let
let foo
- const
const foo
- 何もつけない
foo
などの選択肢がありますよね。
constはその名前から「まあ、よくわかってないけど定数に使うっぽいな」ってなるけど、
じゃあvarとletって何が違うの、そもそも何者!?状態に陥りがちです。
結論から言うと、ほとんどの場合はletを使うべきです!
立ち位置的には、letはvarの問題点を改善したようなものです。
「ん、じゃあvarってなんのために存在するの?」
「varを使ってるサンプルコードいっぱい見るんだけど...」
なんて声が聞こえてきます。
それでは、歴史的な背景を踏まえて見ていきましょう!
letとconstは後から追加された
letとconstは初めからいたわけではありません。
ES2015(ES6)になってから追加されたものです!
そもそもES2015ってなんぞや?ってなりますよね。
なんでES6とES2015の2つの呼び方があるの?5と次が6とかじゃなくて?って私もなりました。
ES2015についてはここが詳しいです!
https://qiita.com/soarflat/items/b251caf9cb59b72beb9b
letとconstはいっぱい追加された機能のひとつですね!
「でもletは機能改善のために導入されたわけだけど、それだけならvar自体を改善したら良かったじゃん」
これは、後方互換性のためです。
varの機能を変更したら、既存のコード全てに影響が出てしまいます。
それによって、突然アプリケーションが動かなくなる可能性だってあります。
なので、varは何も触れずにそのまま残しておいて、新たにletとconstを追加することで対応したのです!
ここで聡明なあなたは、
「確かにそうだけど、それじゃあletとconstを使ってたコードも動かくなっちゃうじゃん!」
と気づいたはずです。
例えばvar let
なんて変数を定義してコードがあったら、動かなくなってしまいますよね?
安心してください、実はletとconstはES6で追加される前からもともと予約語として定義してありました。
つまりさっきのvar let
みたいなコードはもとから使えなかったってことですね。
ちなみに今でも(ES2016)将来的に使用される予約語(implementsやinterface)などは定義されています。
そうでなくても将来的に予約語になる可能性のあるものは避けるべきですね。
予約語一覧が技術書で出てきてもいつも読み飛ばしてた
じゃあletはvarのどこらへんが改善されたの?
1.スコープが変わった
letを使用する場合
とりあえず、このコードを見てください。
let price = 150;
if (price > 100) {
// 100円につき1ポイント
// Math.floorは小数点を切り捨てる
let point = Math.floor(price / 100);
// point = 1;
}
console.log(point) // undefindになるはず
// 変数pointは未定義だよってエラー
ReferenceError: point is not defined
JavaやPHPなど他の言語を学習したことがある人ならお気づきでしょうが、このコードはおかしいですよね!
通常、ifやforなどの内部({}の中で)定義された変数は、その有効範囲はブロックの中に限られます。
これを、ブロックスコープと言います!
varを使用する場合
では、先ほどと同じコードをvarを使って実行してみましょう!
var price = 150;
if (price > 100) {
var point = Math.floor(price / 100);
// point = 1;
}
console.log(point) // undefindになるはず。。。
変更点はletど宣言していた変数をvarにしただけです。
しかし、これを実行すると...
1
予想に反して、正しくコードが実行されましたね!
実は、varで宣言された変数には、ブロックスコープが存在しないのです!
そのため、if文を抜けたあとでも、変数pointは有効であり続けたのです。
ES6以前のJavaScriptには、スクリプト全体から参照できるグローバルスコープと、関数の中でのみ参照できる、ローカルスコープしか存在しなかったのです。
下記のコードのように、関数の中ではスコープは有効です。
function getPoint(price) {
if (price > 100) {
var point = Math.floor(price / 100);
}
}
var price = 150;
getPoint(price);
console.log(point);
// 変数pointは未定義だよってエラー
ReferenceError: point is not defined
このように、関数の中ではスコープは守られるため、即時関数を用いて擬似的にブロックスコープを実現するテクニックが使われていました。
これは、以下の部品で構成されています。 ブロックを関数で包むことによって、スコープを守っているのです。即時関数を使用した、ブロックスコープ
即時関数は、関数を宣言すると同時に実行する書き方です。
(function() {
var price = 150;
if (price > 100) {
var point = Math.floor(price / 100);
}
}());
console.log(point);
Uncaught ReferenceError: point is not defined
- 関数式を使って関数を定義。
- 最後に一対のカッコを付加する。これによって関数が即座に実行される。
- 関数全体をカッコで囲む。
即時関数は、このようにブロックスコープを擬似的に実現するだけでなく、いろいろな用途で使われています。
即時関数の用法についてはココらへんを参照
JavaScriptパターン
変数宣言を省略
最後に、最悪のケースとして、変数の宣言を省略したパターンを見てみましょう。
function getPoint(price) {
if (price > 100) {
point = Math.floor(price / 100);
}
}
price = 150;
getPoint(price);
console.log(point);
1
もはやローカルスコープさえも突き破ってしまいました!
実は、変数宣言を省略すると、必ずグローバル変数になります!
(厳密には、暗黙のグローバルとして扱われ、関数の外でvarを使って宣言された明示的なグローバル変数とはちょっと違う)
基本的に、変数宣言を省略することはありえないと考えてください!
2.変数の再宣言ができなくなった
これも実際にコードを見てもらうのがわかりやすいでしょう!
let fruit = 'apple';
let fruit = 'lemon';
console.log(fruit);
letでfruitという変数を宣言して'apple'を代入後に、誤ってもう一度fruitを宣言しようとしてますね。
これは
Uncaught SyntaxError: Identifier 'fruit' has already been declared
'fruit'は既に宣言されてるよ!って怒られていますね。
letを使用して変数を宣言した場合には、このような意図しない再代入を事前に防いでくれます!
varを使用した場合
じゃあvarを使って再代入使用としたらどうなるの?
この流れから察しがついている方もいらっしゃると思いますが、、、
var fruit = 'apple';
var fruit = 'lemon';
console.log(fruit);
lemon
このように、変数fruitは、'lemon'で上書きされて、そのまま出力されてしまいました。
3.変数の巻き上げ(ホスティング)の挙動が変わった
そもそも変数の巻き上げって?
まずは、変数の巻き上げについて、ざっくりと説明します。
JavaScriptの変数は、スコープの範囲内でどこでも宣言することができますが、その実態はそのスコープの先頭で宣言されたのと同じように扱われます。
console.log(fruit); // 結果 undefined
var fruit = 'apple';
このように、変数を宣言する前に参照しているものの、エラーとはならずundefinedと出力されます。これは、
var fruit;
console.log(fruit); // 結果 undefined
fruit = 'apple';
実態としては、このように書かれているのと同じように扱われているためです。
これは、ローカルスコープでも同様です。
fruit = 'apple'
function func() {
console.log(fruit); // 結果 undefined
var fruit = 'lemon';
console.log(fruit); // 結果 lemon
}
func();
関数の中の1つ目のconsole.logはグローバル変数のappleを出力することを期待しましたが、undefinedが出力されてしまいました。
これはローカルスコープのなかで、fruitが宣言されているため、
fruit = 'apple'
function func() {
var fruit;
console.log(fruit); // 結果 undefined
fruit = 'lemon';
console.log(fruit); // 結果 lemon
}
func();
先程のように実態はこう扱われるわけですね。
このような意図しない挙動を回避するために、JavaScriptでは変数はスコープの先頭でまとめて宣言することがベストプラクティスとされています。
これがletになってどう変わった?
letで宣言された場合には、宣言される間に参照された場合、エラーを吐くようになりました。
console.log(fruit);
let fruit = 'apple';
Uncaught ReferenceError: Cannot access 'fruit' before initialization
他の言語の学習した人からしたら、こっちの方がしっくりきますよね。
ただ、letを使用していても内部的には変数の巻き上げは発生しています。
じゃあ、constは?
constも基本はletと同じく、ブロックスコープを持ち変数の再宣言ができません。
constとletの違う点は、再代入ができないところです。
const fruit = 'apple';
fruit = 'lemon';
console.log(fruit);
SyntaxError: redeclaration of var fruit
let fruit = 'apple';
fruit = 'lemon';
console.log(fruit);
lemon
constで宣言した変数に、あらたに'lemon'を代入しようとしたところ、なにやらエラーが発生してしまいました。
変数'fruit'に再代入はできないよ!って怒ってますね。
(of var のvarは変数の英語名であるところのvarで変数宣言のvarとはまた違うvarでしょうややこし)
ただ、厳密には定数とは違うので、constは再代入ができない変数と覚えておくと良いでしょう。
まとめ
varとletの違いは実感できましたでしょうか?
簡潔に言うなら、「とりあえずvarを使うよりletを使ったほうが安全!」ってことです。
参考書籍 ・サイト
世の中JavaScriptの本っていっぱい出版されていますが、letやcosntが使われていない書籍が多いです。
もちろんそれらの本に問題があるって言うわけではありません!!JavaScriptの根本的な原理は変わっていないですから!
ES2015に対応している書籍なら、ココらへんがおすすめです。
JavaScript本格入門
初めてのJavaScript
また、紙の本でなくとも、Web上の情報でも十分に学習することができます。
JavaScriptPrimer
JavaScript - MDN - Mozilla