はじめに
今まで Javascript で変数宣言をする際に let を使っていたけど、その理由がよくわかっていなかったのでまとめます。
まとめ
・ 変数には「グローバル変数」と「ローカル変数」があるよ。
・ 基本型と参照型の変数で処理の流れが変わるよ。
・ やっぱり、varよりletを使った方がいいよ。
#先ず、最初にスコープのおさらい。
⑴グローバル変数とローカル変数
基本、関数内で定義された変数が「ローカル変数」です。
ローカル変数は関数全体でのみ有効な為、グローバル変数に影響しません。
var scope = "global";
function getScope(){
var scope = "local";
return scope;
}
console.log(getScope()); //=> local
console.log(scope); // => global
上のコードを見てもらうとわかる通り、グローバル変数 scope とローカル変数 scope は同じ変数名でも互いに影響し合わない。
ローカル変数は関数全体でのみ有効なので。
※ちなみに上のコードから var 宣言を取り除いてみます。
scope = "global";
function getScope(){
scope = "local";
return scope;
}
console.log(getScope()); // getScope によって scope に local が格納される。=> local
console.log(scope); // => local
var を外して変数を宣言すると全ての変数がグローバル変数扱いになってしまうので2行目のconsole.log(scope)
でもlocal
が出力されてしまう。
つまりローカル変数がこの場合、存在しないので1行目のconsole.log(getScope())
で関数を呼び出した時点でscope
がlocal
に上書きされてしまっている。
⑵基本型と参照型による変数の処理方法の違い
先ず、前提として基本型は値自体を変数に格納している。
そして参照型は変数の値が格納されているメモリのアドレスを格納している。(格納している場所情報が変数に格納されているイメージ)。
1,基本型
var i = 10;
function decrement(val){
val--;
return val;
}
console.log(decrement(i)); // => 9
console.log(i); // => 10
2,参照型
var arry = [1,2,3,4,5,6];
function deleteElement(val){
val.pop();// 末尾の要素を削除
return val;
}
console.log(arry); // => [1,2,3,4,5,6]
console.log(deleteElement(arry)); // => [1,2,3,4,5]
console.log(arry);// => [1,2,3,4,5]
基本型と参照型の最後に出力されている値を見比べていただくとわかるが、
基本型の場合、引数に入れられたグローバル変数が関数内で処理されるも、その後にグローバル変数を出力すると宣言時の値が出力される。
一方、参照型の場合、引数に入れられたグローバル変数が関数内で処理され、その後にグローバル変数を出力しようとすると関数内で処理された結果が再び出力される。
前提でも話した通り、参照型は変数に値を入れている訳でなく、値のメモリ上のアドレスを格納しているため、一度その場所に格納してある値をいじったら、次にアドレスを参照しても、いじられた後の値が見つかる。 ※一方、基本型の場合は最初に宣言されているグローバル変数が関数内に入り処理されるため、処理された変数は関数内でのみ有効になり、グローバル変数に影響は与えないと理解しました。
varとletの違い
通常、Javaなどではブロック内で定義された変数はブロックの外では使えない為、あらかじめ、ブロックの外で宣言しておく。それによって一度、ブロック内に入った変数もブロックを抜けても外で使えていた。
その方式に乗っ取りかくと以下のようになります。
var i = 4
if (true) {
i++;
}
console.log(i); // => 5
しかしjavascriptにはブロックレベルのスコープが存在しない為、以下のような書き方ができる。
if (true) {
var i = 4;
i++;
}
console.log(i); // => 5
しかし一般的にこの書き方は「スコープは出来るだけ限定すべき」という一般的ルールから逸脱したものであるため、javaに慣れてきた今となっては分かりづらいし、覚えるのが面倒。
#そこで let の使用
上のコードで宣言されているvar i = 4;
のvar
をlet
に置き換えてみよう。
f (true) {
let i = 4;
}
console.log(i); // => Uncaught ReferenceError: i is not defined
見ての通り、ブロックレベルのスコープが働き、ifブロック内で宣言した変数はブロック外ではエラーを吐いてくれるようになりました。これで「スコープは出来るだけ限定すべき」という一般的ルールから逸脱することなく、分かりやすいコードをかけます。
つまり、varはブロックレベルのスコープを認識できず、letはブロックレベルのスコープを認識できることがわかった。以上のことを踏まえてやはり変数宣言はletを使用した方が良いかも。
おまけ
switch分も一つのブロックの為、let宣言は変数の重複エラーになる。
let x = 2;
switch (x) {
case 0:
let value = {x:0};
break;
case 1:
let value = {x:1};
break;
case 2:
let value = {x:2}
break;
}
console.log(value); // => Uncaught SyntaxError: Identifier 'value' has already been declared
解決策
1、最初に変数宣言を行ってしまう
let x = 2;
let value = {x:100}; // ここで変数宣言
switch (x) {
case 0:
value = {x:0}; // let消す
break;
case 1:
value = {x:1}; // let消す
break;
case 2:
value = {x:2} // let消す
break;
}
console.log(value); // => {x:2}
2、ブロック内でvar宣言で変数宣言を行う
let x = 2;
switch (x) {
case 0:
var value = {x:0}; // letをvarに変更
break;
case 1:
var value = {x:1}; // letをvarに変更
break;
case 2:
var value = {x:2}; // letをvarに変更
break;
}
console.log(value); // => {x:2}
スコープは出来る限り限定すべき、という一般的ルールから外れてしまうので⑴の方がいいかな。
最後に
なんとなく使ってモヤモヤしていたvarとletの違いについて改めて勉強することができてよかったです。多分、もうvarは使いません。