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
0會員
11內容數
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
HCY 71的沙龍 的其他內容
在開始討論這兩個陌生的名詞之前,我們先準備一份資料以便後續測試使用。 這筆資料是users的資料,可能是社群平台用來記錄使用者資料的格式。其中users是一個array(有[]包著),裡面包含了3個Object(有{}包著),每個Object內有許多的property-value配對(如id:
Higher-Order Function(高階方程式) 是什麼 Higher-Order Function簡稱HOF,是指一個以function作為參數的function或者回傳function的function,我知道目前聽起來非常抽象,我們舉一個我們之前就已經看過的例子。 以Funct
做選擇的時刻 程式語言中,做選擇是非常常見的一件事。例如:判斷登入者身份是否為管理員、當前設定是否為深色模式等。想像我們有一個交易系統,我們需要判斷使用者帳戶餘額是否足夠購買Apple Vison Pro。首先,我們必須認識if跟else。 if 和 else if跟else是常見的條件判
什麼是Array(陣列) array跟object一樣是一種資料的容器,不同的是array通常用來裝同類別的資料,就像是書架用來收納書。要在書架裡放書跟其他東西也是可以的,但就是有點奇怪,不是大家習慣的做法。 array透過[]宣告。 假設我們要儲存使用者喜歡的顏色這筆資料,每筆資料的類型
今天我們會繼續用CodePen。 為何需要Object(物件) 在開始介紹object之前,我們先來討論object為何需要存在。 想像我們有一筆關於網站使用者的資料,裡面包含使用者名稱、年紀,還有sayHelloToUser()這個function。 我們可以逐個定義、宣告這些資料。
從今天開始往後的的內容會有許多的「註解」,程式不會執行被註解的內容,註解只是方便開發者辨識。JS內的註解主要有兩種。單行註解是在程式碼前方加上//,可以透過快捷鍵cmd(ctrl) + / 完成。多行註解則是用/* 及 */前後包夾要註解的內容。 轉換環境 從上個單元的經驗,有些人可能會發現
在開始討論這兩個陌生的名詞之前,我們先準備一份資料以便後續測試使用。 這筆資料是users的資料,可能是社群平台用來記錄使用者資料的格式。其中users是一個array(有[]包著),裡面包含了3個Object(有{}包著),每個Object內有許多的property-value配對(如id:
Higher-Order Function(高階方程式) 是什麼 Higher-Order Function簡稱HOF,是指一個以function作為參數的function或者回傳function的function,我知道目前聽起來非常抽象,我們舉一個我們之前就已經看過的例子。 以Funct
做選擇的時刻 程式語言中,做選擇是非常常見的一件事。例如:判斷登入者身份是否為管理員、當前設定是否為深色模式等。想像我們有一個交易系統,我們需要判斷使用者帳戶餘額是否足夠購買Apple Vison Pro。首先,我們必須認識if跟else。 if 和 else if跟else是常見的條件判
什麼是Array(陣列) array跟object一樣是一種資料的容器,不同的是array通常用來裝同類別的資料,就像是書架用來收納書。要在書架裡放書跟其他東西也是可以的,但就是有點奇怪,不是大家習慣的做法。 array透過[]宣告。 假設我們要儲存使用者喜歡的顏色這筆資料,每筆資料的類型
今天我們會繼續用CodePen。 為何需要Object(物件) 在開始介紹object之前,我們先來討論object為何需要存在。 想像我們有一筆關於網站使用者的資料,裡面包含使用者名稱、年紀,還有sayHelloToUser()這個function。 我們可以逐個定義、宣告這些資料。
從今天開始往後的的內容會有許多的「註解」,程式不會執行被註解的內容,註解只是方便開發者辨識。JS內的註解主要有兩種。單行註解是在程式碼前方加上//,可以透過快捷鍵cmd(ctrl) + / 完成。多行註解則是用/* 及 */前後包夾要註解的內容。 轉換環境 從上個單元的經驗,有些人可能會發現
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
JSDoc 全名是 JavaScript Documentation,顧名思義是為 JavaScript 所使用的 API 文件,在程式碼內透過註解的方式撰寫,運行後 JSDoc 會自動掃描註解內容,並生成一份網頁版的文件,對於沒有使用 Typescript 開發的專案,也
Thumbnail
這篇內容,將會講解什麼是函式,以及與函式相關的知識。包括函式的簡介、Runtime Function、自訂函式、Script Function 腳本函式、Method 方法。
主要來講宣告函式跟箭頭函式 : 宣告函式(Function Declaration) 語法: function functionName(parameters) { return result; } 特點: 使用 function 關鍵字 函式名稱是必需的 存在函式
就是指變數可以被訪問和使用的範圍,來說一下var、let和const的作用域差異。 var :function example() { console.log(x); // 輸出: undefined 因為變量提升造成的 var x = 5; } 函數作用域或全域作用域 可以重複宣告
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
在網頁開發中,時間與日期的處理是重要且基礎的部分,本文將介紹 JavaScript 中的日期的處理方法,並提供範例程式來協助你理解。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
JSDoc 全名是 JavaScript Documentation,顧名思義是為 JavaScript 所使用的 API 文件,在程式碼內透過註解的方式撰寫,運行後 JSDoc 會自動掃描註解內容,並生成一份網頁版的文件,對於沒有使用 Typescript 開發的專案,也
Thumbnail
這篇內容,將會講解什麼是函式,以及與函式相關的知識。包括函式的簡介、Runtime Function、自訂函式、Script Function 腳本函式、Method 方法。
主要來講宣告函式跟箭頭函式 : 宣告函式(Function Declaration) 語法: function functionName(parameters) { return result; } 特點: 使用 function 關鍵字 函式名稱是必需的 存在函式
就是指變數可以被訪問和使用的範圍,來說一下var、let和const的作用域差異。 var :function example() { console.log(x); // 輸出: undefined 因為變量提升造成的 var x = 5; } 函數作用域或全域作用域 可以重複宣告
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
在網頁開發中,時間與日期的處理是重要且基礎的部分,本文將介紹 JavaScript 中的日期的處理方法,並提供範例程式來協助你理解。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。