Help us understand the problem. What is going on with this article?

var/const/letの違いについて[ 随時更新予定 ]

スコープについて

Wikipediaから引用

プログラミングでのスコープとは、ある変数や関数が特定の名前で参照される範囲のこと。ある範囲の外に置いた変>数等は、通常、その名前だけでは参照できない。このときこれらの変数はスコープ外である、「見えない」といわれる。

変数のアクセス権や生存期間といった話になります。

スコープの種類

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

グローバルスコープ

プログラムのトップレベルで宣言された変数は、グローバル変数となり、プログラム全体のどこからでもアクセス可能なグローバルスコープを持ちます。

var scope = 'global'

// トップレベルからのアクセス
console.log(scope);

// 関数内からのアクセス
(function () {
  console.log(scope);
})();

出力結果

スクリーンショット 2019-04-24 15.15.37.png

ローカルスコープ

グローバル変数以外のすべての変数は。ローカルスコープを持つローカル変数です。

  • 関数スコープと
  • ブロックススコープの2つに分けることができます。

関数スコープ

関数(function)ごとに作られるスコープのことを関数スコープといいます。

関数スコープ内で,var,let,constのいずれかで変数宣言をすると、関数の外部からはアクセスできず、関数の内側からのみ利用可能なローカル変数になります。また、関数の仮引数も同じく関数スコープを持ちます。

function fn(arg) {
  // fn関数の関数スコープを持つローカル変数を定義
  var scope = 'local';
  // fn関数のスコープ内のためのアクセス可能
  console.log(scope); // -> local
  console.log(arg); // -> argument
}

fn('argument');

  // 関数スコープの外側からはローカル変数や仮引数にアクセスできない
  console.log(scope); // -> ReferenceError
  console.log(arg); // -> ReferenceError


出力結果

スクリーンショット 2019-04-24 15.21.33.png

ブロックススコープ

ブロック({})ごとに作られるスコープのことをブロックススコープという。

ブロックススコープ内でletまたはconstを用いて宣言した変数はブロックの外側からはアクセスできずブロックの内側からのみアクセス可能なローカル変数になります。

function fn() {
  // fot文のブロックススコープを持つローカル変数 i を定義
  for (let i = 0; i < 3; i++) {
    console.log(i); // -> 0,1,2
  }
  // for文のブロックススコープの外側からはアクセスできない
  console.log(i); // -> ReferenceError
}
fn();

出力結果

スクリーンショット 2019-04-24 15.29.46.png

ES2015からのローカルスコープ ( メモ )

JavaScriptには当初、グローバルスコープと関数スコープしか存在しなかったため、「関数スコープ=ローカルスコープ」という図式が成り立っており、そのように解説している媒体がほとんどです。

しかし、ES2015でブロックススコープが追加されたことにより、ローカルスコープには関数スコープとブロックススコープの二種類が存在するようになりました。

変数宣言の巻き上げ(ホイスティング)

関数スコープの中で変数宣言を行うと、変数は関数の先頭に引き上げられます。このことを巻き上げやホイスティングと言います。

これがどういうことなのか、具体例と一緒に解説していきます。
次のコードではscope変数を宣言するより前に、console.logscope変数を参照しようとしています。

function fn() {
  console.log(scope);
  var scope = 'local';
}
fn();

変数宣言より前に参照しようとしているのですから、エラーになってしまいそうです。しかし、実際にはundefinedが返されます。

その理由はスコープの途中で宣言したローカル変数の宣言部分が、スコープの先頭に巻き上げられたからです。

先ほどのコードが次のように変わったとするとイメージがしやすいです。

funtion fn() {
  // 変数宣言だけ先頭に巻き上げられる
  var scope;
  console.log(scope); // > undefined
  // 値の代入は元の場所で行われる
 scope = 'local';
}
fn();

巻き上げられるのは宣言だけであり、値の代入自体は元の場所で行われるため、console.logを行った時点では、scopeundefinedを返します。

なおletconstの場合はブロックの先頭に巻き上げはされるのですが、宣言より前でアクセスしようとすると、ReferenceErrorとなってしまう。
このアクセスできない領域を temporal dead zoneと言います。

コード①

// ES5
function driversLicence5(passedTest){

     if(passedTest) {
        var firstName = 'Jphn';
        var yearOfBirth = 1990;

        console.log(firstName + '
        born in ' + yeraOfBirth + ',
        is now officially allowed to
        driver a car.');
      }
}

driversLicence5(true);



// ES6
function driversLicence6(passedTest){

     if(passedTest) {
        let firstName = 'Jphn';
        const yearOfBirth = 1990;

        console.log(firstName + '
        born in ' + yeraOfBirth + ',
        is now officially allowed to
        driver a car.');
      }
}

driversLicence6(true);

正常に機能する

コード②

// ES5
function driversLicence5(passedTest) {

    if (passedTest) {
        var firstName = 'John';
        var yearOfBirth = 1990;
    }

     # 移動
     console.log(firstName + ',born in ' + yearOfBirth +
            ',is now officially allowed to drive a car.');
}
driversLicence5(true);

// ES6
function driversLicence6(passedTest) {

    if (passedTest) {
        let firstName = 'John';
        const yearOfBirth = 1990;
    }

        # 移動
        console.log(firstName + ',born in ' + yearOfBirth +
            ',is now officially allowed to drive a car.');
}
driversLicence6(true);


if文のスコープ外にconsole文を移動させた。
ES6の方ではfirstNameが定義されていないというエラーが表示された
script.js:51 Uncaught ReferenceError: firstName is not defined

これは何故なのか?
letconstはローカルスコープの中の関数スコープではなくブロックスコープだからです。

  • ローカルスコープ
    • 関数スコープ
    • ブロックススコープ ○

let,const宣言だと、ifブロックの中で宣言した変数・定数を、ブロックの外で使用することはできません。

コード③ ( letとconstの違い )

function driversLicence6(passedTest) {

    let firstName;
    const yearOfBirth = 1990;

    if (passedTest) {
        firstName = 'John';
    }

        console.log(firstName + ',born in ' + yearOfBirth +
            ',is now officially allowed to drive a car.');
}
driversLicence6(true);

constif文のブロック外に定義した値に代入する値も定義する必要がある。
letの場合は定義する値だけで値の代入はブロック内で定義しても大丈夫という認識です。

まとめ

関数スコープ

  • functionの中に書かれた
  • functionの中に書かれた変数・関数はその内部でしか変更・参照はできない 外部から参照できないので変更に強いモジュールが作れる

var

  • 巻き上げが起こる
    • JavaScriptは関数内のどこでもvarの宣言を書ける
    • 関数のどこで宣言しても、先頭で定義したものとしてみなされる
    • 先頭部分で使いたい変数は全て定義するのが定石
  • 関数スコープである
    • 関数内ならどこからでも参照できる
var pokemon = 'ライチュウ';
function sing(){
   //JavaScriptは関数内のどこでもvarの宣言を書ける
   //これらの変数は関数のどこで定義しても、先頭で定義したものとして見なされる
   //var pokemon;
   console.log(pokemon);
   var pokemon = 'ピカチュウ';
   console.log(pokemon);
}
sing();

let

  • ブロックススコープ(関数スコープとしても使える)
  • 今までvarで書いてきた所はletで置き換えられる
  • if,for等の{}内のブロックのみで使うブロックスコープが作れるので、スコープを狭く出来るため影響範囲を狭められる
  • 変数の巻き上げは従来どおり起きる
if (true) {
  //if内はスコープが無いのでグローバルスコープ扱い
  var test = 'hoge';
}

//ブロックスコープではないので、アクセスできる
console.log(test);

if (true) {
  //ブロックスコープになる
  let test2 = 'hoge2';
}

//アクセスできない ( 今回のコード②と同じ現象 ) 
console.log(test2);

const

  • 再代入不可能な変数を作る(つまり定数にできる)
  • 再代入しようとするとエラーになる
  • letと同じブロックスコープ(関数スコープとしても使える)
  • 巻き上げは起きる
  • ほとんどは再代入は不要なのでconstを使う
  • constを使っておけば値が変わることが無いので、値が変わるかも?という懸念が無くてコードが読みやすい
  • forのイテレータのような再代入が必要な所のみletを使う
const test3 = 'hoge';

test3 = 'fuga'; //エラー

console.log(test3);

参考記事

JavaScriptのスコープ総まとめ
第1回 スコープの種類とその基本

ES6の新機能: 「let」「const」宣言を調べてみた
var/let/constの使い分けのメモ

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした