Day08 JavaScript Scope & Context

更新 發佈閱讀 9 分鐘


Scope(作用域

不只是JavaScript,scope的概念在每個程式語言裡面都有,但每個語言scope運作邏輯多少有差異。

我們先來舉個例來了解scope:

raw-image
raw-image

隨著myFunction()執行,這段程式毫無疑問的console.log()出了myName這個變數的值’my name’

但如果我們稍微調整console.log()以及變數定義的位置,情況會變得有所不同。

raw-image
這樣寫就會出現錯誤。

這樣寫就會出現錯誤。

如果我們將myName的宣告寫在function內部,則在外面的console.log()就無法取得myName這個變數,所以會出現錯誤顯示myName沒有被定義。


我們明明在function內定義了myName在外部卻不能使用,這就是因為它所在的scope和外部的scope不同,而且外部的scope不能存取內部的scope。它們的關係就像這樣:

raw-image

比較小的scope就像一個小包廂,不能隨意進入。但包廂內的人隨時可以走出來看看外面。這就是為什麼在外面的console.log()存取不到myName,它不能隨意闖入別人的包廂。

以這個例子來說,function內{}包起來的範圍就自己成為一個scope了,被稱為一個block scope


不只是function可以形成scope,在大多數情況下,使用{}包起來的範圍都會自成一個scope。我們刻意用if結構來創造第二層的scope。

raw-image

目前我們有兩個內部的scope,一個在function內,另一個在if內。目前這樣寫也都是正常運作的,因為內部的scope可以存取外部的scope內容。另外,最外層被稱為global scope


我們再稍微改寫一下這個例子:

raw-image

我們多宣告了兩次myName,並且在不同的scope裡都用console.log()印出來。JavaScript會尋找同一個scope裡面有沒有myName,沒有的話才會繼續往更外層(更大的scope)尋找。

raw-image

在現在的情況裡,if那一層scope有myName會印出”my name 2”、function那一層scope也有myName會印出”my name 1”,而global scope則會印出”my name”


請注意,這並不是內部的myName覆蓋掉了外部的。用let關鍵字表示要新增一個變數,就算新變數的名稱和先前定義過的一樣,在JS裡也是可行的。換句話說,在上面例子裡看到的3個myName,就只是3個名字剛好一樣,毫無關係的變數。這是JavaScript scope跟變數的運作方式,在其他語言可能不適用。


如果想要複寫掉原本定義過的myName的話,可以這樣寫:

前面沒有新的let,JS尋找之前定義過的myName並且覆蓋掉它。

前面沒有新的let,JS尋找之前定義過的myName並且覆蓋掉它。

可以注意到在if那層的scope裡,少了let這個關鍵字。這時候JS會做的就不是產生新的變數,而是尋找之前有沒有myName,在同一個scope找不到,就會接著往上層scope找,然後更新它。

這種情況下,所有的myName都被更新成"my name 2"。

這種情況下,所有的myName都被更新成"my name 2"。

在這個例子中,我們保留了所有的console.log(),由於myName被更新了,就只會印出”my name 2”這個值。這和上個例子其實就只差了let,但結果完全不一樣!


letvar

常見的JavaScript宣告關鍵字除了let還有var,在比較早期的JavaScript裡面,開發者幾乎只能用var來宣告變數,但後來推出了let這個關鍵字。這兩個關鍵字有一些些微的差異,最重要而且最大的差別就是它們適用的scope不同。


let如剛才提到的,是block scope,只要有新的{}就會產生新的scope,
var是function scope,只有function內的{}才會產生新的scope。


讓我們舉例說明。

raw-image

我們同樣用剛剛製作一個function裡面在「用if製作另一個scope」的情境為例。對insideIfLet來說if裡面是一個新的scope,新的包廂,所以在外面的console.log()會失敗,它不能存取內部的變數

但詭異的是,使用var在這種情況下console.log()卻可以正常的運作。原因是var 來說,只有function的{}才會開闢新scope,if內的{}不是。

用let時,if{}被視為新的scope,而外部不能存取內部scope,所以出錯。

用let時,if{}被視為新的scope,而外部不能存取內部scope,所以出錯。


好的我知道非常奇怪,不知道為什麼JS的設計師要這樣設計。其實簡單的建議就是不要用var,很多情況下工程師很難直接判斷怎樣的{}是來自function,判斷這件事也會花費很多不必要的時間。使用var會造成scope變得更複雜,還幾乎沒有必要。相較之下,使用let時只要看見{},就能判斷那是一個新的scope,對於自行回顧程式或溝通協作都非常有幫助。

Context

在JavaScript中,還有一個容易讓人混淆的關鍵字叫this,它的代表的值會隨著程式碼執行的環境(context)而改變。不同的context下,this可能指向不同的對象,搞清楚這件事就是我們這個今天的目標。


我們先考慮這個userobject:

raw-image

這個object叫做me,裡面包含firstNamelastName兩個property,還有一個method叫做eat(),它的功能單純就是印出一串string。

raw-image


目前eat()是寫死的,不能根據名稱動態的調整。我們現在希望可以讓eat()存取到firstName以及lastName,這時候就可以用this這個關鍵字。

raw-image
raw-image

而這正是this被設計的目的 — 存取同一個object內的property或是method。滿好理解的,看起來沒什麼問題對吧?但問題就是這個this的運作模式很多時候真的讓人混淆。


我們看看下面的例子。

raw-image

我們在eat()裡面再多定義一個functionhungry(),定義完之後馬上使用它。

raw-image

這時候卻會出現這個結果,this.firstNameundefined,我非常確定沒有打錯字。


這是因為this指向的是「呼叫這個function的object」。以上面的例子來說me.eat()就是me在呼叫eat()eat()內用到的this就是me。但你可以看見hungry()前面並沒有誰在乎叫它,它不是me.hungry()或類似的寫法。這時候hungry()this指的就是程式碼最外層的context(我們會稱作global context,在瀏覽器裡面是Window這個object),反正不是me


關於Window的資訊現在先聽聽就好,總而言之,在一個function前面,呼叫它的角色,就代表this的值。現在你大概知道這個關鍵字有多擾人了。


順帶一提,如果我們是可以調整剛才那個hungry()內的this指向的。

我們指定讓me來callhungry(),它裡面的this就會是me了。

我們指定讓me來callhungry(),它裡面的this就會是me了。

raw-image

用每個function內建的methodcall()bind()apply()也可以),就可以指定this的指向。


當然,我們還有其他辦法可以解決這個this難題,之後會提到。

小結

說了這麼多關於scope和context的內容有點讓人混亂,不過今天只是希望給大家一點概念,不是真的要熟悉每一個細節。重點是在JavaScript裡面,scope和context非常容易讓人混淆(特別是this),在某些比較複雜的情境,資深人員也需要一點時間來搞清楚scope和context的問題。現在的目的是讓大家認識,並且在日後遇到找不出原因的bug的時,可以想到是scope和context的可能性。隨時都可以用console.log()來確認得到的值是不是如預期。


明天我們會討論更多JavaScript裡常見的寫法與其特殊規則,正是因為有這些內容的存在,第一次看到JavaScript才容易不理解,在明天的內容之後,至少能夠看懂那些簡寫是發生了什麼事。


Resource

今日Codepen

連結

Credits

關於我

我是Erkin, 一個網站開發者。
有任何疑問或是想勘誤的話歡迎聯繫。

留言
avatar-img
HCY 71的沙龍
0會員
11內容數
HCY 71的沙龍的其他內容
2024/11/04
這個單元裡我們會繼續用Codepen來製作簡單的To-Do List app。裡面會使用到基本的HTML和CSS來製作UI,別擔心會非常簡單。示範的Codpen同樣會附在今天的內容裡面,如果想看成果的話可以直接點前往。 基礎架構 為了完成這個web app,我們必須先理解HTML、CSS之間
Thumbnail
2024/11/04
這個單元裡我們會繼續用Codepen來製作簡單的To-Do List app。裡面會使用到基本的HTML和CSS來製作UI,別擔心會非常簡單。示範的Codpen同樣會附在今天的內容裡面,如果想看成果的話可以直接點前往。 基礎架構 為了完成這個web app,我們必須先理解HTML、CSS之間
Thumbnail
2024/11/04
在JavaScript裡面,Async和Await應該是搜尋熱度最高的關鍵字了,因為他們相對複雜。我們一步步討論這件事的歷史 — 它們為什麼出現,解決了什麼問題。 JavaScript的Synchronous(同步) 首先我們必須了解JavaScript執行的基本原則 — synchrono
Thumbnail
2024/11/04
在JavaScript裡面,Async和Await應該是搜尋熱度最高的關鍵字了,因為他們相對複雜。我們一步步討論這件事的歷史 — 它們為什麼出現,解決了什麼問題。 JavaScript的Synchronous(同步) 首先我們必須了解JavaScript執行的基本原則 — synchrono
Thumbnail
2024/11/04
今天要談的內容也是JavaScript很核心的部分,即使學完了先前的章節,馬上去看別人寫的JavaScript程式碼還是會看不懂,主要是因為JS開發者會採用各種簡化和替代的寫法,我們一一討論。 Anonymous(匿名) Function 首先我們要討論的是Anonymous(匿名) Fun
Thumbnail
2024/11/04
今天要談的內容也是JavaScript很核心的部分,即使學完了先前的章節,馬上去看別人寫的JavaScript程式碼還是會看不懂,主要是因為JS開發者會採用各種簡化和替代的寫法,我們一一討論。 Anonymous(匿名) Function 首先我們要討論的是Anonymous(匿名) Fun
Thumbnail
看更多
你可能也想看
Thumbnail
創作不只是個人戰,在 vocus ,也可以是一場集體冒險、組隊升級。最具代表性的創作者社群「vocus 野格團」,現在有了更強大的新夥伴加入!除了大家熟悉的「官方主題沙龍」,這次我們徵召了 8 位領域各異的「個人主題專家」,將再度嘗試創作的各種可能,和格友們激發出更多未知的火花。
Thumbnail
創作不只是個人戰,在 vocus ,也可以是一場集體冒險、組隊升級。最具代表性的創作者社群「vocus 野格團」,現在有了更強大的新夥伴加入!除了大家熟悉的「官方主題沙龍」,這次我們徵召了 8 位領域各異的「個人主題專家」,將再度嘗試創作的各種可能,和格友們激發出更多未知的火花。
Thumbnail
vocus 最具指標性的創作者社群──「野格團」, 2026 年春季,這支充滿專業、熱情的團隊再次擴編,迎來了 8 位實力堅強的「個人主題專家」新成員 💫💫💫 從投資理財、自我成長、閱讀書評到電影戲劇,他們各自帶著獨特的「創作超能力」準備在格友大廳與大家見面。
Thumbnail
vocus 最具指標性的創作者社群──「野格團」, 2026 年春季,這支充滿專業、熱情的團隊再次擴編,迎來了 8 位實力堅強的「個人主題專家」新成員 💫💫💫 從投資理財、自我成長、閱讀書評到電影戲劇,他們各自帶著獨特的「創作超能力」準備在格友大廳與大家見面。
Thumbnail
JSDoc 全名是 JavaScript Documentation,顧名思義是為 JavaScript 所使用的 API 文件,在程式碼內透過註解的方式撰寫,運行後 JSDoc 會自動掃描註解內容,並生成一份網頁版的文件,對於沒有使用 Typescript 開發的專案,也
Thumbnail
JSDoc 全名是 JavaScript Documentation,顧名思義是為 JavaScript 所使用的 API 文件,在程式碼內透過註解的方式撰寫,運行後 JSDoc 會自動掃描註解內容,並生成一份網頁版的文件,對於沒有使用 Typescript 開發的專案,也
Thumbnail
這篇內容,將會講解什麼是函式,以及與函式相關的知識。包括函式的簡介、Runtime Function、自訂函式、Script Function 腳本函式、Method 方法。
Thumbnail
這篇內容,將會講解什麼是函式,以及與函式相關的知識。包括函式的簡介、Runtime Function、自訂函式、Script Function 腳本函式、Method 方法。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News