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。

U can’t touch this

在 Dan 的 JavaScript 宇宙中,所有的 primitive values 存在於 code 的外圍圈圈中(如下圖所示),就像遙遠的恆星,你可以使用它們,但沒辦法改變。

image

矛盾的地方

剛剛說到 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 point to Narwhal

我們在這之後能對變數做兩件事。

賦予變數某個值

其中一件事是能夠賦予變數某個值。

pet = 'The Kraken';

Assigning a Value to a Variable

上面所做的就是將線左側(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;

第一行並沒有做太多事情:

image

  • 宣告一個名稱為 x 的變數
    (創造一個從 x 開始的線)
  • 賦予 x 這變數 10 的值
    (將 x 這個變數指向 10 這個值)

第二也很短,但做了一些事:
image

  • 宣告一個名稱為 y 的變數
    (創造一個從 y 開始的線)
  • 賦予 y 變數值,這個值是 x 變數的值
  1. 詢問 x 這個變數的值,沿著 x 變數的線,找到它指向 10 這個值
  2. 從 x expression 得到 10 這個結果。
  3. 因此,賦予 y 變數 10 的值。
  4. y 變數把線的尾端指向 10。

最後,我們到達了第三行:
image

  • 賦予 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,有了這樣的基礎,下次討論更複雜的主題時才可以更順利。