Just JavaScript - Values and Variables 隨意筆記
前言
介紹完上次的 JavaScript 宇宙觀後,今天要來談談變數與值。
先回顧上次的小題目
typeof(value) === "date"
結果是如何呢?
typeof(value) === "date"
的結果永遠都會是 false。
因為 date 不是任何 typeof 會回傳的結果,它不是 primitive types 也不是 functions,所以 typeof date 永遠都會是 object。
typeof 的 bug
typeof(null)
回傳的結果會是 “object”。
即使 null 是 primitive value,但 typeof 會回傳 object,這是個眾所皆知的 bug,但因為改掉的話會讓很多現有的網站掛點,所以就這樣一直放著維持現狀了。
typeof(typeof(value)) 永遠都會是 string
因為 typeof
本身回傳的結果型別是 string,因此對回傳結果使用 typeof
的話,結果就會是 string!
接著繼續
好,讓我們繼續下去囉!先看一小段程式碼:
let reaction = 'yikes';
reaction[0] = 'l';
console.log(reaction);
這段程式碼會回傳什麼呢?
答案是 'yikes'
,如果是在 strict mode 的話,則是會拋出錯誤。對,它不會回傳 'likes'
。
Yikes。
Primitive Values 是 Immutable 的
剛剛的問題有答對嗎?沒答對也沒關係,只是從剛剛的例子我們了解到一件事。
我們沒辦法改變 primitive values。
接下來再用一個小例子解釋吧!字串(屬於 primitive values)以及陣列(屬於 Objects,不是 primitive values)在某些地方有點相近,陣列是一連串的 items,字串則是一連串的字元:
let arr = [212, 8, 506];
let str = 'hello';
對於字串和陣列,你可以用相似的方式來存取的值:
console.log(arr[0]); // 212
console.log(str[0]); // "h"
你可以改變 array 的第一個 Item:
arr[0] = 420;
console.log(arr); // [420, 8, 506]
所以直覺性的,你很容易去假設 string 也可以做相似的改動:
str[0] = 'j'; // ???
但事實上你不能。
所有的 primitive values 都是 immutable 的,「Immutable」的相似詞是 「unchangeable」,意思就是不能改動,Read only 的,你完全沒辦法對 primitive values 上下其手。
一般來說,我們可以直接 set object 的 property,比如這樣:
let a = { test: 1 };
a.test = 100;
console.log(a.test) // 100
但在 primitive values 中,比如 numbers 或 string,是沒辦法 set property 的。
let fifty = 50;
fifty.shades = 'gray'; // No!
50 是 number,也就是 primitive values,因此沒辦法 set property。
在 Dan 的 JavaScript 宇宙中,所有的 primitive values 存在於 code 的外圍圈圈中(如下圖所示),就像遙遠的恆星,你可以使用它們,但沒辦法改變。
矛盾的地方
剛剛說到 primitive values 是 immutable 的,是 read-only,那接下來再用一個例子來測試看看:
let pet = 'Narwhal';
pet = 'The Kraken';
console.log(pet); // ?
按照剛剛 read-only 的理論,輸出的答案應該是…
答案是'The Kraken'
!
怎麼會這樣咧??剛剛不是說 primitive values 是 immutable 的嗎!
把變數當線
我們再看一次剛剛的例子:
let pet = 'Narwhal';
pet = 'The Kraken';
console.log(pet); // "The Kraken"
我們都知道型態為 string 的 value 因為是 primitive value 的關係,所以不能改變,但 pet
這個變數確實變成了 'The Kraken'
,為什麼?
看起來好像有點矛盾,但其實不是,我們說 primitive values 不行改變,但完全沒有談論到變數!
我們必須理清一下我們的概念。
Variables are not values. 變數並不是值
Variables point to values. 變數指向值
在 Dan 的 JavaScript 宇宙中,變數像一條線,有兩個端點和一個方向,它開始於一個名字,結束於指向的某個值。
舉個例子,我們可以將 pet
這個變數指向於 'Narwhal'
這個值。
let pet = 'Narwhal';
我們在這之後能對變數做兩件事。
賦予變數某個值
其中一件事是能夠賦予變數某個值。
pet = 'The Kraken';
上面所做的就是將線左側(left side)的 pet 指向於線右側(right side)的 ‘The Kraken’,pet 會持續指向 The Kraken 這個 value 直到重新被賦值。
左側的 assignment 必須是一條「線」。
右側的 assignment 則必須是 expressions。 可以是很簡單的 2
或是 'hello'
,或是更複雜的 expressions,像是:
pet = count + ' Dalmatians';
count + ' Dalmatians'
是一個 expression,JavaScript 會回應這個 expression(像是 ‘101 Dalmatians’),從現在開始,pet
變數會指向這個值。
如果右側必須是 expresssions 的話,那是否代表數字 2
或是 字串'The Kraken'
也是 expressions 呢?沒錯!這樣的 expressions 稱為 literals
。
讀取變數中的值
我們也可以讀取變數中的值,比如用 console.log
印出。
console.log(pet);
值得注意的是,我們並不是把 pet
這個變數傳給 console.log
(雖然我們口語上可能會這樣說),但我們實際上並不能把變數傳給 functions,我們傳的是 pet
變數目前的值,這背後怎麼運作的呢?
當我們在 console.log 寫下 pet
,是在問 JavaScript 一個問題:「pet
現在的值是什麼呢?」,為了回答這個問題,JavaScript 會沿著 pet 的線攀爬,並且給出這條線終點的「值(Value)」
所以在相同的 expression 在不同的時機會給我們不同的 values。
Nouns and Verbs
爭論「傳一個值」或「傳一個變數」有點無聊,因為大家內心都能理解你要表達的意思,但於此同時,自己內心必須搞清楚在 JavaScript 中,你不能傳一個變數。
這邊是一個小例子:
function double(x) {
x = x * 2;
}
let money = 10;
double(money);
console.log(money); // ?
如果我們把 double(money)
當作傳一個變數進去,那我們會預期 x = x * 2
會把變數 double,但事實上並不是這樣,真正運作的邏輯是:「**找出 money 這個變數的 value,然後把這個 value 傳進去 double。」
Put it toghther
讓我們舉最後一個例子,並一步一步拆解它的運作方式吧~
let x = 10;
let y = x;
x = 0;
第一行並沒有做太多事情:
- 宣告一個名稱為 x 的變數
(創造一個從 x 開始的線) - 賦予 x 這變數 10 的值
(將 x 這個變數指向 10 這個值)
第二也很短,但做了一些事:
- 宣告一個名稱為 y 的變數
(創造一個從 y 開始的線) - 賦予 y 變數值,這個值是 x 變數的值
- 詢問 x 這個變數的值,沿著 x 變數的線,找到它指向 10 這個值
- 從 x expression 得到 10 這個結果。
- 因此,賦予 y 變數 10 的值。
- y 變數把線的尾端指向 10。
最後,我們到達了第三行:
- 賦予 x 值,這個值是 0
(將 x 線的尾端指向 0)
因此在結尾時, x 指向 0 這個值,y 指向 10 這個值。值得注意的地方是,y = x
並不代表把 y 指向 x,我們沒辦法把變數指向彼此!變數永遠指向值,當我們賦予某個值時,都是把左側的變數指向於右側的值。
Dan 說到,一般大家的 Mental Models 都會把變數想成箱子,不過我們目前建立的宇宙觀並沒有提到任何箱子,只有可愛的線們。那為什麼我們不能只想像成把 0 或 10 這個值放進變數裡,而是一定要用線指向某個值的方式來想像呢?
因為這對後續解釋某些概念會比較有幫助,像是 strict equality、object identity 和 mutation,這些概念在後續也會用線的概念來作延伸。
Dan 的 JavaScript 宇宙觀中充滿各種線線。
小回顧
- Primitive values 是 immutable 的。 我們沒辦法改變或影響它們,舉個例子,我們沒辦法幫 string 型態的值 set property,因為它是 primitive values,但 array 的話就可以。
- 變數不是值。 每個變數都指向某個特定的值,我們可以改變變數指向的值,透過
=
這個 assignment operator。 - 變數像是線。這不是 JavaScript 中的概念,但可以幫助我們想像變數指向某個值的狀況。
- 注意矛盾的地方。 如果你在學習某個慨念的時候彼此衝突,先不要沮喪,通常是有更深層的真相潛藏在其中。
練習題
Click here to solidify this mental model with a few short exercises.
這是 Dan 提供的練習題,答案會在下次的信件進行講解,建議做了一週再前往下一週,這可以鞏固你目前在建立的 mental model,有了這樣的基礎,下次討論更複雜的主題時才可以更順利。