這篇有目錄
1. 何謂原始值
2. 何謂複合值
3. 結論
前言 & 廢話:
這篇要來學習一下有關 javascript 的基本常識,這篇很適合初學者看
我把它訂為一個系列,因為要讓 Javascript 完全發揮威力的話,基礎太重要了! (我也是現在才慢慢建立)
但是首先從我們平常在開發中可能會遇到的問題開始說起,之後幾篇再討論別的
我們常常在開發時寫的很爽,但相信大部分的大家"一開始"都是從模仿學習 or 摳比來的
因此沒有建立很好的觀念,也不太了解這個語言背後的運作方式 (像我)
有時會遇到一些跟自己預期不同的輸出結果,當下只想著上網找解決方式
但是卻沒有去想為何會如此,所以這篇要來對 javascript 這語言重新打底一下
好好了解一下 javascript 的語言特性 (此篇基於ES5)
在開始之前要先定義些名詞,這兩個名詞要先懂了才能繼續下去
這名詞不是我唬爛,是書上寫的
1. 何謂原始值:
要開始之前首先定義一下何謂原始值
在 Javascript 裡面像 12345、'12345'、true、false、null、undefined 都被視為原始值
以上分別是數字、字串、布林、空、未定義,被稱作原始值的原因是他們已經無法再複雜了
它們是 javascript 中最低階的基準
2. 何謂複合值:
複合值之所以叫複合值,因為他們裡面可能會有一個或多個的原始值,像是物件或物件實字
都提到物件了,順便提一下 Javascript 內建的九個物件建構式
Javascript 內建的九種物件建構式
內建的物件建構式有以下幾種:
Nunmber()、String()、Boolean()、Object()、Array()、Function()、Date()、RegExp()、Error()
所謂建構式就像個模具,模具裡已經定義好一些屬性或方法了,當用 new 關鍵字時就會真正建立一個物件
就像下面這樣:
var a = new String("test");
而物件實字就像是這樣:
或是像陣列
以上都叫做複合值,了解這兩個名詞後,接下來要來看看一個問題
問題來了!!!!
Q:內建建構式的使用與比對問題
上面有提到了內建的九種物件建構式都是複合值的一種,下面來看一下有可能會遇到的問題
這邊舉的例子都是平常開發可能會出包的
先想一下,以下程式碼會輸出什麼
然後下面這段會輸出什麼?
打開 F12 貼上去 run 之後答案是這樣
啊靠! 是為何?
明明只是差在一個建構式有 new 一個沒 new,結果居然完全不同
原因是因為 String() 如果使用 new 來建構的話其形態會像上面說的是一個物件,如下:
但如果你沒有透過 new 去建構,就會被轉換為原始值,如下的型態為 string
轉換步驟是這樣: Javascript 為了反應表達式,會先把他轉成物件,但是之後會將物件的性質都移除
並且將它改回原始值,於是當我們開發時萬一沒注意到這運作方式可能就出包了
接下來也是有關比對的問題
Q:原始值與複合值的比對問題
這一題也是我們平常開發容易出包的一題
請問一下輸出結果是 true or false ?
答案是 true,一樣的字串相比為 true 很好理解,大家直接用腳趾頭思考就回答出來了
再看下面這題,請問輸出的結果是 true or false
答案是 false
啊靠! 為何? 內容不是都完全一模模一樣樣!
這個問題的原因是,Javascript 對複合值的比對使用的是參考記憶體位址
(by value or by reference 下節討論)
因為這兩個物件在記憶體中是兩個不同的位址 (雖然它們在記憶體中儲存的值是一樣的)
而上面針對原始值是單純的比對記憶體中的"值",所以才為 true
上面的例子把它改成用字串比就會是 true 了
那如果這樣呢 ?
經過上面問題的洗禮,大家可以用肚臍回答了,答案就是 false
如果你的肚臍告訴你錯誤的答案也沒關係,請看下面的解釋
上面說過了,String 建構式如果使用 new 關鍵字的話,其形態就會是 object
而複合值在 Javascript 中比對方式是利用記憶體中的位址
a、b 在記憶體中都是各自獨立的位址,所以比對為 false
接下來看一下 Javascript 中的 by reference、by value 問題
Q:By Reference or By Value ?
首先程式設計師應該沒有不清楚這兩者差別的,但如果你不知道就容我解說一下
基本上會造成上面幾個問題的原因,跟 Javascript 對於原始值與複合值運作方式有很大關係
用以下例子來看比較清楚
答:a 跟 b 都是 {foo: "haha"}
這就是位址的參考
在 var a = {}; 時會給 a 在記憶體中一個位址
而 var b = a; 則代表著宣告 b 的同時,把 a 在記憶體中的位址給 b 參考了
因此 a 和 b 其實都是指向同一個記憶體位址,所以只要一改變該位址中的值,a 跟 b 都會同時被更改
再來看看傳值 By Value
答: a = 4,b = 3
當 var a = 3; 的時候會在記憶體中配置一個位址給 a
當 var b = a; 的時候,會在記憶體中配置一個位置給 b,然後將 a 的值存到 b 在記憶體中的位址
故 a 跟 b 在記憶體中是不同的位址,等於各自改各自在記憶體中的值
用下面別人的圖來描述一下
(出處:http://www.technolizard.com/base/technology/javascript/javascript-function-object/)
再來最重要的是,Javascript 到底是 by reference 還是 by value?
用剛剛上面兩個例子來看,如果你之前還在睡沒關係,但下面兩句話要記起來!
如果你宣告的是原始值 (數字、字串、布林....之類的),那就會以 by value 的方式來運作
但如果你宣告的是複合值 (物件、物件實字、陣列....等) 就會用 by reference 來運作
這個觀念非常重要,因為 javascript 中 if 的判斷式對於原始值與複合值有所不同 (請看上題)
且如果不知道物件、陣列....等複合值是 by reference 的話,很容易值被蓋掉了都不知道!
再來是一個很容易遇到的問題
那如果中途把複合值 (ex : object) 改變成原始值呢?
String {0: "h", 1: "e", 2: "l", 3: "l", 4: "o", length: 5, [[PrimitiveValue]]: "hello"}
給予原始值的變數就會變成 by value
原本 a、b 是參考到記憶體裡同一位址,當將原始值 "你好" 給 a 之後,a 就有自己獨立的記憶體位址了
其實不管是複合值還是原始值都一樣,只要重新給予值就會變另一個新的記憶體位址
如以下的例子:
a 動態的給予一個物件 {n:2},但此時 b 還是 {n:1}
唯一有一點例外,除非你是改參考物件屬性中的值,就還會是 by reference
重要的結論:
除非更改的是物件中的屬性,不然立即更改變數值的同時都會是另一個記憶體位址
Q:在函數中的 By Value、By Reference
上面幾個問題描述了在使用 if 判斷時,原始值跟複合值的差異之處
現在來看看下面的例子,請問下面的輸出結果 a 為何 ?
答案: 10
我們對 add function 傳入變數 a ,值為 10 的原始值並執行
函數內執行完後,函數外 a 的值還是 10
代表函數內的 a 跟 函數外的 a 已經是在不同的記憶體位址,所以是 By Value
那再看下一個例子
答案: { num: 20 }
我們將 a 這個複合物件傳入至 add 函數中,並在函數中將物件的 num 屬性 + 10
函數執行完之後,函數外 a 物件的 num 變成 20了!
代表著傳入函數的變數 a 與函數內的 a 共用同一個記憶體位址
所以函數內的 a.num + 10,函數外的值也被加10了
所以代表著是 By Reference
3. 結論:
Javascript 中變數的型別會影響到數值傳遞的方式
如果在"執行時" or "宣告時"給的是原始值就會是 By Value,如果是複合值就會是 By Reference
而 By Value 與 By Reference 會影響到判斷式與給於值時,有不同的動作行為
當使用 if 判斷時,複合值會使用記憶體中的位址來判斷是否相等
而原始值會使用記憶體位址中的值來判斷
另外,建構式如果使用 new 關鍵字的話,型態就會是複合值 object
如果沒有 new 就是原始值,會使用記憶體位址中的值來判斷
在函數的參數使用上也是一樣,如果傳入的是原始值,就會以 By Value 運作
如果傳入的是複合值,就會以 By Reference 運作
下篇待續.......
Javascript 基礎打底系列 (一) - 常見的出包狀況解析 (關於By Value、By Reference)
Javascript 基礎打底系列 (二) - null、undefined、NaN 的差異與檢查方式
Javascript 基礎打底系列 - By Value、By Reference 的課後考題!
Javascript 基礎打底系列 (三) - 邏輯運算子,與短路邏輯 (short circuit logic) 的應用
留言列表