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
債券投資,不只是高資產族群的遊戲 在傳統的投資觀念中,海外債券(Overseas Bonds)常被貼上「高資產族群專屬」的標籤。過去動輒 1 萬甚至 10 萬美元的最低申購門檻,讓許多想尋求穩定配息的小資族望而卻步。 然而,在股市波動劇烈的環境下,尋求穩定的美元現金流與被動收入成為許多投資人
Thumbnail
債券投資,不只是高資產族群的遊戲 在傳統的投資觀念中,海外債券(Overseas Bonds)常被貼上「高資產族群專屬」的標籤。過去動輒 1 萬甚至 10 萬美元的最低申購門檻,讓許多想尋求穩定配息的小資族望而卻步。 然而,在股市波動劇烈的環境下,尋求穩定的美元現金流與被動收入成為許多投資人
Thumbnail
透過川普的近期債券交易揭露,探討債券作為資產配置中「穩定磐石」的重要性。文章分析降息對債券的潛在影響,以及股神巴菲特的操作策略。並介紹玉山證券「小額債」平臺,如何讓小資族也能低門檻參與海外債券市場,實現「低門檻、低波動、固定收益」的務實投資方式。
Thumbnail
透過川普的近期債券交易揭露,探討債券作為資產配置中「穩定磐石」的重要性。文章分析降息對債券的潛在影響,以及股神巴菲特的操作策略。並介紹玉山證券「小額債」平臺,如何讓小資族也能低門檻參與海外債券市場,實現「低門檻、低波動、固定收益」的務實投資方式。
Thumbnail
解析「債券」如何成為資產配置中的穩定錨,提供低風險高回報的投資選項。 藉由玉山證券的低門檻債券服務,投資者可輕鬆入手,平衡風險並穩定財務。
Thumbnail
解析「債券」如何成為資產配置中的穩定錨,提供低風險高回報的投資選項。 藉由玉山證券的低門檻債券服務,投資者可輕鬆入手,平衡風險並穩定財務。
Thumbnail
相較於波動較大的股票,債券能提供固定現金流,而玉山證券推出的小額債,更以1000 美元的低門檻,讓學生與新手也能參與全球優質企業債投資。玉山E-Trader平台即時報價、條件式篩選與清楚的交易流程等特色,大幅降低投資難度,對於希望分散風險、建立穩定現金流的人來說,玉山小額債是一個值得嘗試的理財起點。
Thumbnail
相較於波動較大的股票,債券能提供固定現金流,而玉山證券推出的小額債,更以1000 美元的低門檻,讓學生與新手也能參與全球優質企業債投資。玉山E-Trader平台即時報價、條件式篩選與清楚的交易流程等特色,大幅降低投資難度,對於希望分散風險、建立穩定現金流的人來說,玉山小額債是一個值得嘗試的理財起點。
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進行開發。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News