透過下面的例子瞭解javascript是怎麼運作的

1
2
3
4
5
6
7
8
9
10
var foo = "bar";
function bar() {
var foo ="baz";
}
function bax(foo){
foo = "bam";
bam = "yay";
}

我原本的認知是第一行的code就是一次的宣告指令

但其實可以透過另一種方式來看這段程式碼

就是把自己當成naive top down compiler

並把compile拆成宣告跟執行兩個步驟

編譯

  1. 第一行,嘿 Scope global ,我找到了一個叫做 foo 的 var 宣告
  2. 第三行,嘿 Scope global ,我找到了一個叫做 bar 的 function 宣告
  3. 第四行,嘿 Scope bar ,我找到了一個叫做 foo 的 var 宣告
  4. 第七行,嘿 Scope blobal ,我找到了一個叫做 baz 的 function 宣告
  5. 第七行的 foo , 嘿 Scope baz ,我找到了一個叫做 foo 的 var 宣告

執行

  1. 第一行的 var 在執行階段已經不在,因為在 compile 階段已經宣告了 foo 成為一個 var,所以第一行在做的事情就是,向當前的 scope 去詢問,嘿 global scope,你有LHS reference 叫做foo的嗎?scope 會回應說有找到,並且直接將RHS assign 進LHS。
    • 在 = 左邊為LHS 也就是target, 右邊為RHS 也就是source
    • 若 reference 不是 LHS,那就會是RHS
  2. 假設我們執行了 function bar,這時就會問,嘿 bar scope,你有LHS reference 叫作foo的嗎?scope 會回應說有找到,並且直接將RHS assign 進LHS。
  3. 第七行,嘿, baz scope 你有LHS reference 叫作foo的嗎?scope 會回應說有找到,並且直接將RHS assign 進LHS。
  4. 第九行,嘿, baz scope 你有LHS reference 叫作bam的嗎?這時scope 會回應說沒有,這時候就會往外一層scope去找,再問,嘿,global scope,你有LHS reference 叫作bam的嗎? 若在 unstrict 模式下 global scope 會回應說,「有啊,我幫你創造了一個」,這時 bam就變成了一個global variable了。

這邊稍微講一下 undeclaredundefined的差別

undeclared:沒在任何的scope底下被宣告

undefined:已宣告的變數但沒有任何實際的值

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var foo = "bar"; //global scope
function bar() {
var foo = "baz"; // bar scope
function baz(foo) {
foo = "bam"; // baz scope
bam = "yay"; // global scope
}
baz();
}
bar(); //
foo; // "bar" RHS
bam; // "yay" RHS
baz(); // ReferenceError RHS

在 LHS reference 沒被宣告的情況下,scope會自動幫你宣告

但 RHS reference 沒被宣告則會觸發 ReferenceError

function declaration

1
2
3
4
5
6
7
8
9
10
11
12
function bar() { // bar 是在global scope底下
var foo = "baz";
function baz(foo) {
foo = bar;
foo;
}
baz();
}
foo(); // undeclared
bar();

function expressions

1
2
3
4
5
6
7
8
9
10
11
12
var foo = function bar() { // bar 是在自己的scope底下
var foo = "baz";
function baz(foo) {
foo = bar;
foo;
}
baz();
}
foo();
bar(); // Error

block scope

1
2
3
4
5
6
7
8
9
var foo;
try {
foo.length;
}
catch (err) { // err in catch block scope
console.log(err); // typeError
}
console.log(err); // ReferenceError

IIFE (Immediately-invoked function expression)

可以避免global變數被我們寫的code所影響

1
2
3
4
5
6
7
8
var foo = "foo";
(function(){ // 建立一個獨立的scope
var foo = "foo2";
console.log(foo); // "foo2"
})();
console.log(foo); // "foo"

let (ES6)

let 是在ES6 syntax中新的語法,讓變數只存在在block中,let不會hoisting,所以必須將let宣告放在最前面

1
2
3
4
5
6
7
8
9
function foo() {
var bar = "bar";
for (let i=0; i<bar.length; i++){ // 讓i只存在在for loop的scope裡面
console.log(bar.charAt(i));
}
console.log(i); // RefferenceError
}
foo();
1
2
3
4
5
6
7
8
9
10
11
12
function foo(bar) {
if (bar) {
let baz = bar;
if (baz) {
let bam = baz;
}
console.log(bam); //Error
}
console.log(baz); //Error
}
foo("bar");

Ref:

frontendmasters advanced-javascript

You-Dont-Know-JS