ブラウザの動作原理とスコープ


スコープに関する問題集
-----

以下のコードを実行すると、どのような結果が表示されるでしょうか？
ブラウザの動作原理を理解した上で、各問題の答えを最後に解説します。


    //問題１
    var n = 100;
    function foo() {
        n = 200;
    }
    foo();
    console.log(n); // 何が出力されますか？
-----------------

    //問題２
    function foo(){
        console.log(n)
        var n= 200
        console.log(n)
    }
    var n = 100
    foo()
-----------------

    //問題３
    var n= 100
    function foo1(){
        console.log(n)
        var n= 300
        console.log(n)
    }
    function foo2(){
        var n= 200
        console.log(n)
        foo1()
    }
    foo2()
    console.log(n)
-----------------

    //問題４
    var n = 200
    function foo(){
        console.log(n)
        return
        var n = 100
    }
    foo()    

　
　

ブラウザの動作原理解説
-----


　
<b>基本動作の流れ</b>
　
１、解析フェーズ:
- JavaScriptエンジンはコードを解析し、変数や関数をメモリに登録します
- varで宣言された変数はundefinedで初期化されます
　
 
２、関数宣言は完全な状態で登録されます
- コードを上から順に実行
- 変数への値の代入や関数呼び出しが行われます
　

<b>変数宣言と実行の流れ（具体例）</b>

    console.log(num1); // step1
    
    var num1 = 20;     // step2
    var num2 = 30;     // step3
    var result = num1 + num2; // step4
    
    console.log(result); // step5


<b>準備段階（解析時）</b>
ブラウザはコードを実行する前に、次の準備を行います：
１、グローバルオブジェクト（GO）を作成
２、変数宣言を事前登録（値をundefinedで初期化）
３、関数宣言を完全な形で登録

    globalObject = {
        // 組み込みオブジェクト
        String: class,
        Date: class,
        setTimeout: function,
        
        // 宣言された変数（初期値はundefined）
        num1: undefined,
        num2: undefined,
        result: undefined
    }

<b>コード実行</b>

実行コンテキスト（GEC）を作成し、上から順に処理：

    step１： console.log(num1)
    num1を検索 → 現在の値はundefined
    出力：undefined
    
    step２：  var num1 = 20
    num1に20を代入
    メモリ更新：num1: 20
    
    step３：  var num2 = 30
    num2に30を代入
    メモリ更新：num2: 30
    
    step４：  var result = num1 + num2
    計算：20 + 30 = 50
    resultに50を代入
    メモリ更新：result: 50
    
    step５：  console.log(result)
    resultの現在値は50
    出力：50

<b>最終的なメモリ状態</b>

    globalObject = {
        String: class,
        Date: class,
        setTimeout: function,
        
        // 更新された値
        num1: 20,
        num2: 30,
        result: 50
    }


ブラウザの動作原理：関数の処理フロー
-----

    foo();
    function foo() {
        console.log("foo");
    }

このコードがエラーにならない理由：

準備段階で，関数宣言は完全な形で登録される
メモリ上に関数の実体（0xa00など）が作成される
グローバルオブジェクトに参照が保存される

上記の例は、

    globalObject = {
        // ...（他のプロパティ）
        foo: 0xa00 // 関数実体への参照
    }


0xa00はアドレスで、内部メモリに保存

    //(foo)0xa00

    [[scope]]:parent scope
    excute body
    

上記のは、関数の解析です。

関数実行時の流れはこちらの通りです。

    foo(123)
    function foo(num){
        console.log(m)
        var m = 10
        var n = 20
        console.log("foo")
    }


<b>実行コンテキスト作成：</b>
- 新しい実行環境（FEC）がスタックに追加される
- アクティベーションオブジェクト（AO）が作成される


<b>初期状態：</b>

    //AO(activation object)
    
    AO = {
        num: 123,  // 引数で受け取った値
        m: undefined, // var宣言はホイスティング
        n: undefined  // var宣言はホイスティング
    }

<b>関数の実行</b>

- console.log(m) → undefinedを出力
- m = 10を実行 → AOが更新
- n = 20を実行 → AOが更新

        AO = {
            num: 123,
            m: 10,
            n: 20
        }

<b>スコープチェーンの実際</b>

    var message = "hello";
    
    function bar() {
        var message = "bar";
        console.log(message); // "bar"
        foo();
    }
    
    function foo() {
        console.log(message); // "hello"
    }
    
    bar();

<b>実行プロセス</b>

１、bar()実行時：
- barのAOにmessage: "bar"が登録
- 自分のAOを優先して参照

　
２、foo()実行時：
- foo内にmessage宣言がない
- スコープチェーンで外側を探索
- グローバルのmessage: "hello"を発見


最初の問題の説明
-----

    //質問１
    var n = 100
    function foo(){
        n= 200
    }
    foo()
    
    console.log(n); // 結果: 200

グローバルオブジェクト（GO）に変数nと関数fooを登録
コード実行:
n = 100 でGOを更新
foo() を実行
関数内のn = 200:
関数内にnの宣言がない → スコープチェーンでGOのnを更新
console.log(n) は更新後のGOのnを参照
  
-----
    //質問２
    function foo(){
        console.log(n) // undefined
        var n= 200
        console.log(n) // 200
    }
    var n = 100
    foo()

GO: { n: undefined, foo: 0xa00 }
n = 100 でGOを更新
GO: { n: 100, foo: 0xa00 }
foo() のAOを作成（変数nをホイスティング）
AO: { n: undefined }
1回目のconsole.log(n) → undefined
n = 200 でAOを更新
AO: { n: 200 }
2回目のconsole.log(n) → 200


-----
    //質問３
    var n= 100
    function foo1(){
        console.log(n) // undefined
        var n= 300
        console.log(n) // 300
    }
    function foo2(){
        var n= 200
        console.log(n) // 200
        foo1()
    }
    foo2()
    console.log(n) // 100
    

GO: { n: 100, foo1: 0xa00, foo2: 0xb00 }
foo2()の実行:
AOにn = 200を登録

AO(foo2): { n: 200 }
console.log(n) → 200

foo1()の実行:
ホイスティングによりAOを作成

AO(foo1): { n: undefined }
1回目のconsole.log(n) → undefined

n = 300でAOを更新

AO(foo1): { n: 300 }
2回目のconsole.log(n) → 300

最終出力:
グローバルのnは変更されていない

console.log(n); // 100


    
-----
    //質問４
    var n = 200
    function foo(){
        console.log(n); // undefined
        return
        var n = 100
    }
    foo()  

GO: { n: 200, foo: 0xa00 }
関数実行:
ホイスティングによりAOを作成

AO: { n: undefined }
console.log(n) → undefined

returnで関数終了（n = 100は実行されない）

重要なポイント:
var n はホイスティングされるが、代入は実行されない