JavaScript(ES6)の変数スコープの違いによる挙動確認




ES6で追加されたletとconst、以前からあるvarとグローバル変数について、スコープの違いによる挙動をまとめました。

関数スコープで挙動確認

var

今までよく使われていたvarは、スコープが関数となります。
関数内で変数が宣言されている場合は関数内のスコープで変数が有効になります。

var myvar = 'g変数';

function f1() {
    var myvar = 'f1変数';
    console.log(myvar);
}
f1();// 関数内でmyvarが宣言および初期化されているので「f1変数」が表示される
function f2() {
    console.log(myvar);
}
f2();// 関数内でなmyvarは宣言されていないがグローバル変数として宣言および初期化されているので「g変数」が表示される

let

関数レベルでのスコープはvarと同じ。

let mylet = 'g変数';

function f1() {
    let mylet = 'f1変数';
    console.log(mylet);
}
f1();// 関数内でmyletが宣言および初期化されているので「f1変数」が表示される
function f2() {
    console.log(mylet);
}
f2();// 関数内でなmyletは宣言されていないがグローバル変数として宣言および初期化されているので「g変数」が表示される

const

関数レベルでのスコープはvar,letと同じ。

const myconst = 'g変数';

function f1() {
    const myconst = 'f1変数';
    console.log(myconst);
}
f1();// 関数内でmyconstが宣言および初期化されているので「f2変数」が表示される
function f2() {
    console.log(myconst);
}
f2();// 関数内でなmyconstは宣言されていないがグローバル変数として宣言および初期化されているので「g変数」が表示される

ブロックスコープで挙動確認

var

varは関数スコープなので、ブロックの影響は受けません。

function f1() {
    var myvar = 'f1変数';
    console.log('f1-1 -> ' + myvar);
    {
        var myvar = 'f1ブロック変数';
        console.log('f1-2 -> ' + myvar);
    }
    console.log('f1-3 -> ' + myvar);
}
f1();// f1-1は「f11変数」、f1-2,f1-3は「f1ブロック変数」が表示される

let

letはブロックスコープなので、ブロックが終了すると参照できなくなります。

function f1() {
    let mylet = 'f1変数';
    console.log('f1-1 -> ' + mylet);
    {
        let mylet = 'f1ブロック変数';
        console.log('f1-2 -> ' + mylet);
    }
    console.log('f1-3 -> ' + mylet);
}
f1();// f1-1は「f11変数」、f1-2は「f1ブロック変数」、f1-3は「f1変数」が表示される

const

constもletと同じブロックスコープなのでブロックが終了すると参照できなくなります。

function f1() {
    const myconst = 'f1変数';
    console.log('f1-1 -> ' + myconst);
    {
        const myconst = 'f1ブロック変数';
        console.log('f1-2 -> ' + myconst);
    }
    console.log('f1-3 -> ' + myconst);
}
f1();// f1-1は「f11変数」、f1-2は「f1ブロック変数」、f1-3は「f1変数」が表示される

スコープ内で変数を再宣言した場合

var

function f1() {
    var myvar = 'f11変数';
    console.log('f1-1 -> ' + myvar);
    {
        var myvar = 'f1ブロック変数';
        console.log('f1-2 -> ' + myvar);
    }
    console.log('f1-3 -> ' + myvar);

    var myvar = 'f11変数(再宣言)';
    console.log('f1-4 -> ' + myvar);

}
f1();// f1-1は「f11変数」、f1-2,f1-3は「f1ブロック変数」、f1-4は「f11変数(再宣言)」が表示される

let

letはブロック内で再宣言できないのでJavaScriptのエラーが発生します。
Chromeだと「Uncaught SyntaxError: Identifier ‘mylet’ has already been declared」というエラーメッセージが表示されました。

function f1() {
    let mylet = 'f11変数';
    console.log('f1-1 -> ' + mylet);
    {
        let mylet = 'f1ブロック変数';
        console.log('f1-2 -> ' + mylet);
    }
    console.log('f1-3 -> ' + mylet);

    let mylet = 'f11変数(再宣言)';
    console.log('f1-4 -> ' + mylet);

}
f1();// ※1の行でmyletがすでに宣言されているというJavaScriptエラーが発生します。

const

constもletと同じくブロック内で再宣言できないのでJavaScriptのエラーが発生します。
Chromeだと「Uncaught SyntaxError: Identifier ‘myconst’ has already been declared」というエラーメッセージが表示されました。

function f1() {
    const myconst = 'f11変数';
    console.log('f1-1 -> ' + myconst);
    {
        const myconst = 'f1ブロック変数';
        console.log('f1-2 -> ' + myconst);
    }
    console.log('f1-3 -> ' + myconst);

    let myconst = 'f11変数(再宣言)';// ※1
    console.log('f1-4 -> ' + myconst);

}
f1();// ※1の行でmyconstがすでに宣言されているというJavaScriptエラーが発生します。

スコープ内で変数を変更する

var,let

var,letで宣言した変数はスコープ内で自由に変更することができます。

const

constで宣言した変数はスコープ内で変更した場合、JavaScriptエラーが発生します。
Chromeでは「Uncaught TypeError: Assignment to constant variable.」と表示されました。

function f1() {
    const myconst = 'f11変数';
    console.log('f1-1 -> ' + myconst);

    myconst = 'f11変数(変更)';
    console.log('f1-2 -> ' + myconst);

}
f1();// constで定義した変数は変更できないので変更処理でエラーが発生する。

宣言前に変数を参照する

変数を宣言しないで変数を参照した場合、当然エラーが発生します。
Chromeでは「Uncaught ReferenceError: myvar is not defined」と表示されました。
ブロック内で変数宣言はしているが宣言より前に変数を参照した場合、varとlet,costは挙動が変わります。
varの場合はブロック内で宣言していればエラーではなくundefinedとなります。
let,constは宣言前に参照するとJavaScriptエラーが発生します。
Chromeでは「Uncaught ReferenceError: mylet is not defined」と表示されました。

var

function f1() {
    console.log('f1 -> ' + myvar);
}
f1();// 変数が定義されていないのでJavaScriptエラーが発生する。

function f11() {
    console.log('f11-1 -> ' + myvar);
    var myvar = 'f11ブロック変数';
}
f11();// f11-1は「undefined」が表示される

let

function f12() {
    console.log('f12-1 -> ' + mylet);
    let mylet = 'f12変数';
}
f12();// f12-1はJavaScriptエラーが発生。

const

function f13() {
    console.log('f13-1 -> ' + myconst);
    const myconst = 'f13変数';
}
f13();// f13-1はJavaScriptエラーが発生。

var変数の巻き上げ

関数スコープであるvarの場合、スコープ内で変数が定義されている場合、変数宣言より前に参照してもその宣言自体は有効になります。
ただし、変数の代入は最初に代入したあとからでないとあたいの参照はできないため、変数代入前はundefinedとなります。
var変数の巻き上げは、varで宣言した変数の宣言部分は関数ブロックの先頭に記述されているものとして処理されるため発生します。
let,constでは本現象は発生しないので変数み宣言でエラーとなります。

まとめ

JavaScriptで変数を利用する場合、基本的にvarは使わずに変更しない変数はconst、変更する可能性がある変数はletで宣言するのがよく、varを使う場合は関数の先頭にまとめて定義することを意識することで予期せぬ問題を防ぐ可能性が高くなります。