iT邦幫忙

2022 iThome 鐵人賽

DAY 24
0
自我挑戰組

JavaScript101與人生幹話系列 第 24

JavaScript101與人生幹話-執行環境、作用域

  • 分享至 

  • xImage
  •  

執行環境、作用域

1.JavaScript是怎麼運作的?

JavaScript是一種直譯語言,也就是一邊執行一邊翻譯給機器看的語言。

1-1JavaScript直譯器轉換過程

我們所寫的程式碼->語法基本單元化(Tokenizing)->抽象結構樹(Abstract Syntax Tree)AST->代碼生成

1-1-1語法基本單元化(Tokenizing)

在這個階段會把所寫的code還不會執行任何動作,會先分類是什麼東西。

如下圖

var a = 'a';

把上面的程式碼的符號與單字一一列出

1-1-2抽象結構樹(Abstract Syntax Tree)

這個時候會依照上一個步驟語法基本單元化(Tokenizing)的結果來判斷目前程式碼在昨什麼。
下圖是定義一個變數,型態為var

參考資料

2.執行的錯誤情境 LHS, RHS

2-1 LHS

代表賦值到左邊變數例如

let a = 'a'; //a被賦值'a'

'a' = 1 //Uncaught SyntaxError: Invalid left-hand side in assignment

上圖的錯誤訊息表示left-hand side的變數無法被賦值。
上面的錯誤是在編譯時出錯的,程式碼不會執行。

2-2 RHS

代表從右側取得變數

let a = 'a';
console.log(a)//從右側取得變數

console.log(b)//Uncaught ReferenceError: b is not defined

上圖的錯誤訊息是在執行階段發生,表示找不到b這個變數。

參考資料

何謂 LHS、RHS 錯誤?

3.語法作用域(Lexical scope)與範圍鍊(Scope Chain)

語法作用域也稱靜態作用域,表示程式碼在編譯的時候就已經確定了作用位置。
動態作用域則是調用函式時才會確定作用域。
JavaScritp是靜態作用域。

作用域分為全域作用域與區域作用域,如果區域作用域沒有使用的變數則會向外層尋找,值到全域就叫做範圍鍊。

let a = 'a';
function fn1 () {
    console.log(a)
}

function fn2 () {
    let a = 'b'
    fn1()
}
fn2()// 'a'

執行fn2時雖然fn2內部有宣告變數 a = 'b'但是JavaScript為靜態作用域,所以開始執行fn1時要回到第2行執行,又因為fn1內沒有變數a所以向外層尋找變數a,找到變數a時執行console.log(a)。

4.執行環境(Excution context)與執行堆疊(Excution stack)

4-1執行環境(Excution context)

當一個函式執行時就會產生一個執行環境,相同函式可以重複被呼叫產生出不同的執行環境。

4-2與執行堆疊(Excution stack)

function mornimgWork () {
    eatBreakfirst ()
    brushTeeth ()
}
function eatBreakfirst () {
    console.log('eatBreakfirst')
}
function brushTeeth () {
    console.log('brushTeeth')
}
mornimgWork()// 'eatBreakfirst' 'brushTeeth'

由下圖說明執行堆是如何進行的 圖片來源六角學院JS核心篇課程

由左至右分
1.開始執行mornimgWork,mornimgWork內有eatBreakfirst
2.執行eatBreakfirst後eatBreakfirst退出執行堆疊
3.執行mornimgWork內的brushTeeth後brushTeeth退出執行堆疊
4.mornimgWork退出執行堆疊

5.提升(Hoisting)

5-1創造環境與執行

5-1-1變數的宣告

var a = 'a'
console.log(a)

1.先創造環境,此時記憶體空間會分割出一個空間記住a,此時的a還是undefined,相當於物件的key。
2.執行的時候才把'a'賦值在a上,相當於物件的value

5-1-2函式陳述式

與變數的宣告不同,函數陳述式在創造階段就載入,所以在創造階段的時候函式就可以運行了。範例如下

callA() // 'A'
function callA () {
    console.log('A')
}

5-1-3函式表達式

與函式陳述式不同函式表達式是在執行階段才把函式的內容載入,所以必須在執行階段才能執行函式,範例如下


var callA = function () {
    console.log('A')
}
callA() // 'A'

如果是下面的範例

callA() // callA is not a function
var callA = function () {
    console.log('A')
}

則會因為callA沒有被賦值(函式)就執行,在執行的時候callA為undefined無法執行。

5-1-4函式優先往前移

範例1如下


var callA = function () {
    console.log('B')
}
function callA () {
    console.log('A')
}
callA() // 'B'

會發現結果是B,原因如下

//準備階段

function callA () {//函式會比優先宣告變數優先,並且函示的內容會在準備階段時帶入
    console.log('A')
}
var callA
// 執行階段
callA = function () {//在執行階段的時候把callA被賦值
    console.log('B')
}
callA() // 'B'

範例2如下

function callA () {
    console.log('A')
}
callA() // 'B'
function callA () {
    console.log('B')
}
callA() // 'B'

那為什麼兩者都會是'B'呢?原因如下

//準備階段

function callA () {//函式會比優先宣告變數優先,並且函示的內容會在準備階段時帶入
    console.log('A')
}
function callA () {//函式會比優先宣告變數優先,並且函示的內容會在準備階段時帶入,
    //如果宣告同樣的函式名稱則以最後宣告的為主。
    console.log('B')
}
// 執行階段
callA() // 'B'
callA() // 'B'

範例3

whosName()  // undefined
function whosName() {
  if (myName) {
    myName = '杰倫';
  }
  console.log(myName)  
}
var myName = '小明';
console.log(myName); // '小明'

原因如下

// 準備階段
// whosName把函式內容代入
function whosName() {...}
// 變數
var myName

// 執行階段

// 這個時候 myName還是undefined,所以whosName()內的if不會執行
whosName()  // undefined

myName = '小明'                     

6.undefined與 is not defined

undefined

在定義一個變數的時候,分為兩的步驟,先定義變數,在定義變數的值

var a 
console.log(a) //  undefined

以上範例只有定義變數而已,但是值沒有定義,所以出現undefined

is not defined

連變數都沒有定義出現在紅字報錯

console.log(a) //  a is not defined

7.記憶體存放與釋放

7-1 回收機制(Garbage collection)

下圖為執行環境與執行堆疊
綠色格子為執行堆疊,黑色框為所占記憶體空間。

圖片來源六角學院JS核心篇課程

當sayHi()執行完之後sayHi()所占的記憶體空間就會被釋放。


圖片來源六角學院JS核心篇課程

當doSomithing()執行完之後doSomithing()所占的記憶體空間就會被釋放。


圖片來源六角學院JS核心篇課程

當沒有物件參考的時候函式或變數所佔的記憶體空間就會被回收。

8.執行緒與同步、非同步

8-1單執行緒(Single Thread)

JavaScript為單執行緒的語言,也就是說JS一次只能執行一個函式

8-2非同步(Async)

JavaScript會依序執行含式,但是遇到了非同步的函式;例如 setTimeOut()就會移到事件佇列(Event queue)

8-2-1事件佇列(Event queue)


圖片來源六角學院JS核心篇課程
上圖示在說明doWork()開始執行的時候的事件佇列,當執行到callSomeone()時裡面的setTimeout就會被移到事件佇列等到所有doWork()的內的所有函式都執行完之後,在執行事件佇列的內容。

延伸閱讀

setTimeout+Promise+Async输出顺序?很简单呀!

JS 時間循環 - 宏任務與微任務

人生幹話-都是雙標仔

在公司經過許多的嘗試後找各種漏洞與關說,最終還是失敗終於放棄在2020年初上市,我以為可以稍微輕鬆一點的時候,老闆覺得我們是做醫材的所以風險管理很重要,公司權責分化是老闆說什麼就是什麼,但是照老闆的意思來東西還是出包,那也不是老闆的錯一定是做的人的錯,另一方到了三不管地帶的工作,例如跨部門的協調這種他會猶豫要由哪個部門主導的工作屎缺就會塞到QA,但這個工作的權責應該在PM,但是PM在該公司只是老闆的傳聲筒與出氣筒,基本上沒有對產品的主導權,新人PM常常會提出自己對產品的看法然後就被老闆洗臉了,但照老闆的想法執行但是出包了又會說你是PM要有自己的想法,怎麼感覺跟我第一份工作的主管一樣是雙標仔。


上一篇
JavaScript101與人生幹話-箭頭函式與箭頭函式的this
下一篇
JavaScript101與人生幹話-比較詳細一點的物件說明
系列文
JavaScript101與人生幹話30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言