MENU

JavaScript構文変数関数・メソッド

【JavaScript】 変数・関数の巻き上げってなんだ?

更新日:2021/01/15

 

JavaScriptの変数や関数は実行時に巻き上げられます。

 

これってどんな意味なのか、お伝えします!

 

巻き上げとは

 

変数はvar/let/constのどれかを使って定義します。

 

実際には定義前に定義されています。

 

 

意味がわからない・・・


 

次のコードを見てください。

 

console.log( x );  // undefined
console.log( y );  // ReferenceError: y is not defined
var x = 100;

 

後ろで定義されている x は、undefinedと表示されました。
しかし全く定義されていない y は、ReferenceErrorと表示されました。

 

xについては、コードの評価時に次のような処理がおこなわれています。

 

var x; // ←定義が巻き上げられた!
console.log( x );  // undefined
x = 100;

 

このようにコード実行時に、定義だけが先頭に移動されることを巻き上げといいます。

 

 

巻き上げの罠

 

今ではほとんどvarは使用されることがありません。
ですが説明の都合上、ここではvarを使用しています。

 

JavaScriptの初心者がよくやってしまう、巻き上げが原因の失敗を紹介します。

 

JavaScriptは関数内部から、外側の変数を参照できます。

 

var x = 100;

 

function a(){
    console.log( x );  // 100
}

 

 

100と表示されました。


 

では100と表示された後に、新しい変数を定義して200と表示させましょう。

 

var x = 100;

 

function a(){
    console.log( x ); // 100と表示させたい ← undefinedになる
    
    var x = 200;
    console.log( x ); // 200と表示させたい
}

 

 

undefinedになってしまいました!


 

関数内部で巻き上げがおこって、外部の変数を参照できなくなっているのです。

 

var x = 100;

 

function a(){
    var x; // ローカル変数として定義された
    console.log( x ); // 値をセットしていないのでundefined
    
    x = 200;
    console.log( x ); // 200
}

 

undefinedは、定義されていないという意味。
でも実際は、undefinedという値が変数にセットされています。

 

変数の巻き上げ

 

varの巻き上げと、let/varの巻き上げは少し違う点があります。

 

巻き上げの初期値

 

var/let/constで定義した変数を、定義前に表示してみます。

 

console.log( x );   // undefined
console.log( y );   // ReferenceError: can't access lexical declaration `y' before initialization
console.log( z );   // ReferenceError: can't access lexical declaration `z' before initialization
var x = 100;
let y = 200;
const z = 300;

 

実際には、ReferenceErrorが出た時点で処理が止まります。
そのため3番目のコンソール出力は処理されません。

 

varで定義した変数は、undefinedと表示されました。
その他はReferenceErrorで処理が止まりました。

 

let/constもvar同様に巻き上げがおこっています。
しかしvarが暗黙的にundefinedがセットされるのに対し、let/constは何もセットされません。

 

そのためエラーとなって処理が停止しているのです。

 

ブロックでの巻き上げ

 

varは関数スコープで評価されます。
そのため、関数内の同名変数は全て同じものを指します。

 

function a(){
    var x = 100;
    if( x > 0 ){
        var x = 300;
    }
    for( var i = 0 ; i < 3 ; i ++){
        var x = 500 * i;
    }
}

 

上の関数は評価時に、次のように内部の変数が巻き上げられます。

 

function a(){
    var x; // xが巻き上げられた
    x = 100;
    if( x > 0 ){
        x = 300;
    }
    for( var i = 0 ; i < 3 ; i ++){
        x = 500 * i;
    }
}

 

一方、let/constはブロックスコープです。
{ } で囲まれた範囲で巻き上げがおこります。

 

function a(){
    let x = 100;
    if( x > 0 ){
        let x = 300;
    }
    for( let i = 0 ; i < 3 ; i ++){
        let x = 500 * i;
    }
}

 

巻き上げで、次のように処理されます。

 

function a(){
    let x;
    x = 100;
    if( x > 0 ){
        let x;
        x = 300;
    }
    for( let i = 0 ; i < 3 ; i ++){
        let x;
        x = 500 * i;
    }
}

 

ブロックごとに定義されていますね。

関数の巻き上げ

 

関数の定義も巻き上げがおこります。

 

関数宣言の巻き上げ

 

次のコードは関数宣言の前に、その関数を実行しています。

 

// いろいろな処理
f();
// いろいろな処理
function f(){
    // 処理
}

 

コード評価時に、巻き上げで次のようになります。

 

function f(){
    // 処理
}
// いろいろな処理
f();
// いろいろな処理

 

関数がコードの先頭に移動するので、問題なく動きます。

 

 

 

関数式の巻き上げ

 

次のコードは関数式で関数を定義して、その関数を実行しています。

 

f();
const f =  function(){
    // 処理
}

 

これは動きません。

 

これはあくまで変数の巻き上げだからです。

 

コード実行時に、次のように処理されています。

 

const f;
f();
f =  function(){
    // 処理
}

 

巻き上げがおこらないとどうなる?

 

巻き上げがおこらないと、コードの途中で変数の参照先が変わります。

 

例えば次のようなコードで巻き上げがおこらないとしたら、どうでしょうか?

 

let x = 100; // (a)

 

function a(){

 

    console.log( x ); // (1) : (a) を参照

 

    let x = 200; // (b)

 

    console.log( x ); // (2): : (b) を参照
}

 

コードの途中で関数外部の変数から、関数内部の変数に参照先が変わっていますね。

 

この程度のコードなら問題ありませんがもっと長いものになると、いつ参照先が変わったのかを把握するのが難しくなります。
その結果バグを生み出す原因にもなります。

 

 

 

同一スコープ内では、同じ名前の変数は同じものを参照していた方がプログラマ的にも楽なのです。

内部的な処理の話

 

ここまで巻き上げについてお伝えしてきましたが、JavaScriptの内部では巻き上げのための処理をおこなっていません。

 

JavaScriptはコードを実行する前に、コードを読み取り内容を評価して実行できるような形に変換します。
そのとき{ }や関数毎に環境レコードという変数や関数名の一覧を作成しています。

 

ただしletやconstは名前が用意されただけで、中身がありません。

 

巻き上げ

 

そしてコードが実行されると、この環境レコードから変数が検索されます。
目的の変数が環境レコードに無い場合、外側の環境レコードを探しに行きます。
変数が見つかっても、値がセットされていないとエラーになります。

 

これはスコープチェインという仕組みで、その仕組みのために環境レコードが作成されます。

 

コードを実行する時点で、変数が用意されているため、変数が巻き上げられたように見えます。
そのため便宜上、巻き上げという言葉を使って説明しているのです。

まとめ

 

 

巻き上げは実際に定義が移動しているのではなく、移動しているように見える処理を内部的におこなっています。

 

 

巻き上げは、これを使ってコードをどうこうするという話ではありません。

 

しかし知識として頭の片隅において、プログラムを作成する必要はありそうです。

 

参考記事:
【JavaScript】 ECMAScript2020から変数宣言を読み解く

記事の内容について

 

こんにちはけーちゃんです。
説明するのって難しいですね。


「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

そんなときは、ご意見もらえたら嬉しいです。

ご意見はこちら。
https://affi-sapo-sv.com/info.php

【お願い】

お願い

■このページのURL


■このページのタイトル


■リンクタグ