☛ JavaScript 如何運作 ?
我們撰寫的JS程式碼,需要透過電腦解譯後才能使用。
程式語言分為 直譯式語言 與 編譯式語言。
- 編譯式語言 - 撰寫原始碼後,預先編譯生成代碼後才會執行,好處是,當在預先編譯時即可除錯,效能通常較好。
- 直譯式語言 - JavaScript 就是屬於直譯式語言(interpreted language) - 執行前沒有經過編譯,原始碼透過直譯器生成代碼,錯誤會直接反映在環境中(eg: console),優點是彈性較高。
直譯的過程
var name = "andy" ;
- 語法基本單元化 (Tokenizing) - 將所有的字詞解析出來
- 抽象結構樹 AST(Abstract Syntax Tree)
- 代碼生成
- 運行
☛ 執行的錯誤情境 LHS /RHS
LHS
left-hand side 用於賦予值到左側的變數上
var name = "Andy";
錯誤的情境 : 當左側不是一個變數時..
'bella' = "Andy"; //錯誤 Invalid left-hand side in assignment 編譯過程中發生的錯誤
練習題 : 為甚麼會出現LHS錯誤?
//<P>Hi</p>
function ClickThis(){console.log('click')}
$('p').click() = ClickThis; //執行的函式不能再被賦予值。
//正確寫法
function ClickThis(){console.log('click')}
$('p').click(ClickThis); //執行的函式不能再被賦予值。
當有LHS錯誤時,特別注意左邊的值是否可以被賦予。
RHS
right-hand side 取值來自於右側的變數上
var name = "Andy"; //LHS
var man = name ; //使用RHS取的name的變數,並用LHS賦予值到變數man
console.log(name) //RHS
錯誤的情境 :
var name = "Andy"; //LHS
console.log(na) // referenceError : na is not defined. 在執行階段發生錯誤
在JS的運行過程中,若有一個錯誤未修正,則無法繼續運行!!!
☛ JS-語法作用域(Lexical scope)
作用域又分為兩種:
- 靜態(語法)作用域 - 變數的作用域在語法解析時,就已經確定作用域,且不會再改變,JS就屬於此。
- 動態作用域 - 變數的作用域在函式調用才決定。
//靜態作用域示範 - 執行前就確定作用域 var value =1; // 全域作用域 function fn1(){ console.log(value);} function fn2(){ var value = 2; //函式作用域 fn1(); } fn2(); // 1
以JS來看,因為JS是靜態作用域,因此答案為 1 。//動態作用域示範 - 函式調用時才決定作用域,所以會在調用的地方往上一層的函式尋找。 var value =1; // 全域作用域 function fn1(){ console.log(value);} function fn2(){ var value = 2; //函式作用域 fn1(); } fn2(); // 2
JS作用域是層層向內
全域 - 內層
當變數在內層找不到時,會向外層找,若全域也找不到,就會出現undefined.
☛ 執行環境與執行堆疊
執行環境
當我們在執行一個函式時,會產生一個執行環境(限制作用域/this)。執行一次就會產生一個執行環境,並產生自己的變數。
在還未執行函時前,不會產生執行環境,亦不會產生變數。
全域環境global
除了函式會產生執行環境,全域也有屬於自己的全域執行環境,當網頁或後段node.js開啟時,執行環境就會建立並同時產生window或global的變數以及this(window or global === this ,但this的指向會隨環境改變)。
執行堆疊 Execution Stack
使用console-resouce查看每個步驟
- 網頁開啟 - 全域執行環境
運行doNow() - doNow()執行環境
運行sayHi() - sayHi()執行環境
執行環境一層一層堆疊,執行完成後,一層一層離開。function sayHi(name){ var greeting = 'hi'; return greeting + '' + name; } function doNow(){ var mom = '媽媽'; console.log(1, sayHi(mom)); } doNow(); // hi 媽媽
- 網頁開啟 - 全域執行環境
執行openDoors();
執行openTheDoors(1); //開第1扇門
openTheDoors退出
執行Forloop
執行openTheDoors(i);//開第2扇門
openTheDoors退出
... 重複執行與退出 ...到次數執行完畢
退回 openDoors();
退回 全域function openTheDoors(){ return '開第' + num + '扇門'; } function openDoors(){ openTheDoors(1); for (Var i=2;i<5;i++){ openTheDoor(i); } openDoors();
☛ 範圍鍊 Scope Chain
當函式內部本身沒有所需變數時,向外尋找的範圍,尋找的範圍與執行環境沒有關聯。
練習: person這個變數,在不同的環境下執行,結果分別為何?
var person ="mon";
function sayHi(){
console.log('hi'+''+person);
}
function GoOut(){
var person = "dad";
function meetAuntie(){
var person = "auntie";
console.log('hi'+''+person);
}
sayHi();
meetAuntie();
}
sayHi(); //"hi mon"
GoOut(); //"hi mon" "hi auntie"
步驟:
1.執行sayHi(); //"hi mon"
函式內部沒有person變數,向外層(全域)尋找。
3.執行GoOut();
4.執行GoOut裡面 外層的sayHi(); //"hi mon"
GoOut的執行環境不影響sayHi()的範圍鍊。
5.執行GoOut裡面的meetAuntie(); //"hi auntie"
如果今天meetAuntie();內部沒有person變數,就會向外層尋找變//"hi dad"