スコープに関する問題集
以下のコードを実行すると、どのような結果が表示されるでしょうか?
ブラウザの動作原理を理解した上で、各問題の答えを最後に解説します。
//問題1
var n = 100;
function foo() {
n = 200;
}
foo();
console.log(n); // 何が出力されますか?
//問題2
function foo(){
console.log(n)
var n= 200
console.log(n)
}
var n = 100
foo()
//問題3
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)
//問題4
var n = 200
function foo(){
console.log(n)
return
var n = 100
}
foo()
ブラウザの動作原理解説
基本動作の流れ
1、解析フェーズ:
- JavaScriptエンジンはコードを解析し、変数や関数をメモリに登録します
- varで宣言された変数はundefinedで初期化されます
2、関数宣言は完全な状態で登録されます
- コードを上から順に実行
- 変数への値の代入や関数呼び出しが行われます
変数宣言と実行の流れ(具体例)
console.log(num1); // step1
var num1 = 20; // step2
var num2 = 30; // step3
var result = num1 + num2; // step4
console.log(result); // step5
準備段階(解析時)
ブラウザはコードを実行する前に、次の準備を行います:
1、グローバルオブジェクト(GO)を作成
2、変数宣言を事前登録(値をundefinedで初期化)
3、関数宣言を完全な形で登録
globalObject = {
// 組み込みオブジェクト
String: class,
Date: class,
setTimeout: function,
// 宣言された変数(初期値はundefined)
num1: undefined,
num2: undefined,
result: undefined
}
コード実行
実行コンテキスト(GEC)を作成し、上から順に処理:
step1: console.log(num1)
num1を検索 → 現在の値はundefined
出力:undefined
step2: var num1 = 20
num1に20を代入
メモリ更新:num1: 20
step3: var num2 = 30
num2に30を代入
メモリ更新:num2: 30
step4: var result = num1 + num2
計算:20 + 30 = 50
resultに50を代入
メモリ更新:result: 50
step5: console.log(result)
resultの現在値は50
出力:50
最終的なメモリ状態
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")
}
実行コンテキスト作成:
- 新しい実行環境(FEC)がスタックに追加される
- アクティベーションオブジェクト(AO)が作成される
初期状態:
//AO(activation object)
AO = {
num: 123, // 引数で受け取った値
m: undefined, // var宣言はホイスティング
n: undefined // var宣言はホイスティング
}
関数の実行
-
console.log(m) → undefinedを出力
-
m = 10を実行 → AOが更新
-
n = 20を実行 → AOが更新
AO = { num: 123, m: 10, n: 20 }
スコープチェーンの実際
var message = "hello";
function bar() {
var message = "bar";
console.log(message); // "bar"
foo();
}
function foo() {
console.log(message); // "hello"
}
bar();
実行プロセス
1、bar()実行時:
- barのAOにmessage: "bar"が登録
- 自分のAOを優先して参照
2、foo()実行時:
- foo内にmessage宣言がない
- スコープチェーンで外側を探索
- グローバルのmessage: "hello"を発見
最初の問題の説明
//質問1
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を参照
//質問2
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
//質問3
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
//質問4
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 はホイスティングされるが、代入は実行されない