0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaScriptのvar,let,constの違いを理解する。ついでにスコープも理解する。

Last updated at Posted at 2021-09-07

概要

変数宣言について、
「変数はvarかlet、定数はconst。」
「varはもう使うな。」
とか聞いたことあるけど、詳細を理解していないので調べてみました。

違いを理解するための前提知識としてスコープの理解が必要だったので、それも調べてみました。

変数宣言

変数の宣言とは、変数名を登録し値を格納するための領域をメモリに確保することです。

JavaScriptでは変数を使う際、変数の宣言が推奨されています。(※宣言しなくても動くが注意が必要。詳細はおまけ1で)
変数宣言をするための命令として以下3つがあります。
letconst はES2015から登場した新しい命令です。

  • var
  • let
  • const
//宣言
var test;
console.log(test); //->代入していないのでundefined

//代入
test = 1;

//宣言時に代入してもOK(初期化という)
var test2 = 2;

//宣言せず代入することもできる
test3 = 3;

3つの違いは?

まとめるとこんなかんじです。

var let const
再代入 ×
再宣言 × ×
ブロック内で変数を宣言した場合のスコープ ブロックではない ブロック ブロック
変数の巻き上げ undefined ReferenceError ReferenceError
それぞれ詳しくみていきましょう。

再代入

再代入とは、宣言した変数名に対して別の値を代入することです。
var,letは再代入できますが、constはできないです。

「定数の宣言ではconstを使う」とよく聞きますよね。これはconstが再代入を許可しないからです。

//OK
var a = 'hoge';
a = 'fuga';

//OK
let b = 'hoge';
b = 'fuga'; 

//NG  
const c = 'hoge';
c = 'fuga';  // ->TypeError: Assignment to constant variable.

再宣言

再宣言とは、既に宣言している変数名で再度変数の宣言を行うことです。
varは再宣言ができますが、let,constはできないです。

再宣言できてしまうと、意図せず同じ変数名で宣言してしまったことで不具合につながる可能性があります。
varを使う場合要注意ですね。

//OK
var a = 'hoge';
var a = 'fuga';

//NG
let b = 'hoge';
let b = 'fuga'; ->Identifier 'b' has already been declared

//NG  
const c = 'hoge';
const c = 'fuga'; ->Identifier 'c' has already been declared

ブロック内で変数を宣言した場合のスコープ

ブロック内で変数の宣言を行った場合、var,let,constのどれを使ったかで変数のスコープが変わります。
varはブロックの外側のスコープになり、(関数内で宣言したならローカルスコープ、トップレベルで宣言したならグローバルスコープ)、
let,constはブロックスコープになります。
詳細にいく前に、スコープについて整理します。

スコープとは?

スコープとは、変数が参照できる範囲を決めるの概念のことです。
JavaScriptにおいてスコープには以下があります。

  • グローバルスコープ
  • ローカルスコープ
  • ブロックスコープ

グローバルスコープ

スクリプト全体から参照が可能。グローバルスコープを持つ変数をグローバル変数という。

ローカルスコープ(関数スコープ)

宣言された関数内でのみ参照できる。(本によっては関数スコープ書いてる場合もあり。)ローカルスコープを持つ変数をローカル変数という。

function test(arg) {
  // ローカル変数を宣言
  var a = 'local';
  console.log(a); // -> local
  // 関数の仮引数もローカルスコープをもつ
  console.log(arg); // -> hoge
}
test('hoge');

// 関数の外側からはローカル変数や仮引数を参照できない
console.log(a); // -> Uncaught ReferenceError: a is not defined
console.log(arg); // -> Uncaught ReferenceError: arg is not defined 

ブロックスコープ

ES2015から追加されたスコープです。ブロック({})の範囲のみ参照できます。
例)if文やループ

function test() {
  
  if (true){
    // ブロック内で変数を宣言(ブロックスコープ)
    let a = 'test';
  }
  // ブロックスコープの外側からは参照できない。
  console.log(a); // ->ReferenceError: a is not defined
}
test();

var,let,constでブロックスコープの扱いが違う

スコープについて整理したところで、var,let,constの違いに話を戻します。
ブロックスコープはES2015から追加された概念なので、let,constは対応していますが、ES2015以前からあったvarは対応していないです。

function test() {
  
  if (true){
    // ブロック内で変数を宣言
    var var_test = 'var';  // ローカルスコープ
    let let_test = 'let';  // ブロックスコープ
    const const_test = 'const';  // ブロックスコープ
  }
  console.log(var_test);  //  ローカルスコープなので関数内から参照できる
  console.log(let_test);  // ->ReferenceError: let_test is not defined ブロックの外から参照できない
  console.log(const_test);  // ->ReferenceError: const_test is not defined ブロックの外から参照できない
}
test();

変数の巻き上げ

ローカルスコープの説明の中で以下のように記述しました。

宣言された関数内でのみ参照できる。

つまり、宣言された関数内であれば、宣言より前の行から参照できる ということです。どゆこと??

function test() {
  console.log(a); // -> ???
  var a = 'test';
}
test();

console.log(a);
の結果はどうなるでしょうか?まだ宣言されてないのでReferenceErrorが出そうに思えますが...
.
.
.
正解はundefinedです。
変数aはローカルスコープをもっており、関数内全体で参照可能です。2行目console.log(a)の時点では変数aの宣言だけがされており、値の代入はされていないため、undefinedとなります。
以下のように書いた場合と同じになります。

function test() {
  var a;
  console.log(a); // -> undefined
  a = 'test';
}
test();

このように、変数の宣言がスコープの先頭で行われたようになる挙動を「巻き上げ」または「ホイスティング」といいます。

ここまでの例では宣言にvarを使ってきましたが、let const だとどうなるでしょうか?

function test() {
  console.log(a); // -> ReferenceError: Cannot access 'a' before initialization
  let a = 'test';
}
test();

エラーになります。constでも同様にエラーになります。
let,constでも巻き上げは起こっていますが、値が代入される前に参照しようとするとReferenceErrorになります。

varを使うときは要注意です。varを使うなら、変数の宣言はスコープの先頭で行うべきですね。

##まとめ
再度、それぞれの違いをまとめます。

var let const
再代入 ×
再宣言 × ×
ブロック内で変数を宣言した場合のスコープ ブロックではない ブロック ブロック
変数の巻き上げ undefined ReferenceError ReferenceError

おまけ1 ~変数宣言しなかったらどうなる?~

以下のコードでは、関数内の変数aは宣言せず、いきなり代入しています。
宣言しなくても動くので、これ自体は問題ないです。
では、console.log(a)の結果はどうなるでしょうか?

var a = 'global';
function test(){
    a = 'local';
}
test();
console.log(a);

.
.
.

グローバル変数のaを参照しているので、globalと言いたいところですが、結果はlocalです。
var let const を使って宣言しなかった変数はグローバル変数とみなされるからです。

var a = 'global';
function test(){
    a = 'local';  // グローバル変数aに再代入
}
test();
console.log(a); // 再代入後のaを参照しているので、結果はlocalになる

「宣言しなくても動くんだから、しなくてもいいや」とか思ってると、意図せずグローバル変数を上書きしてしまいかねません。
原則変数宣言は行いましょう。

おまけ2 ~switch文でletを使うときは気を付けよう~

ブロック内でletで変数宣言した場合、スコープはブロックスコープになる、という話をしました。
ここで注意したいのがswitch文をつかった条件分岐です。

まず、if文を使ってこのように書いた場合、エラーにはなりません。
変数bはブロックスコープを持つので、各条件のブロック内でのみ有効となるため、let b = 2; は変数の再宣言になりません。

let a = 1;
// ここから
if (a === 1){
    let b = 1;
}
// ここまでで1つのブロック
else if (a === 2){
    let b = 2;
}
else{
    let b = null;
}

これをswitch文を使って以下のように書くと、エラーになります。
switch文全体で1つのブロックになるので、let b = 2;は変数の再宣言になります。

let a = 1;
// ここから
switch(a){
    case 1:
        let b = 1;
        break;
    case 2:
        let b = 2; // -> SyntaxError: Identifier 'b' has already been declared
        break;
    default:
        let b = null
        break;
}
// ここまでで1つのブロック

switch文を使うなら、以下のように、switch文のブロックの外で変数bを宣言すればOKです。

let a = 1;
// ブロック外で宣言
let b;
switch(a){
    case 1:
        b = 1;
        break;
    case 2:
        b = 2; 
        break;
    default:
        b = null;
        break;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?