這篇有目錄

1. 何謂原始值

2. 何謂複合值

Q:內建建構式的使用與比對問題

Q:原始值與複合值的比對問題

Q:By Reference or By Value ?

Q:在函數中的 By Value、By Reference

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");

而物件實字就像是這樣:

var obj = {
   name : "eason",
   action : "haha"
}

或是像陣列

var arr = [1, 2, 3, 4, 5];
 

以上都叫做複合值,了解這兩個名詞後,接下來要來看看一個問題

 

問題來了!!!!

 

Q:內建建構式的使用與比對問題

上面有提到了內建的九種物件建構式都是複合值的一種,下面來看一下有可能會遇到的問題

這邊舉的例子都是平常開發可能會出包的

先想一下,以下程式碼會輸出什麼

題目:
var a = String("test");
var b = "test";
console.log(a === b);

然後下面這段會輸出什麼?

題目:
var a = new String("test");
var b = "test";
console.log(a === b);

打開 F12 貼上去 run 之後答案是這樣

啊靠! 是為何?

明明只是差在一個建構式有 new 一個沒 new,結果居然完全不同 

原因是因為 String() 如果使用 new 來建構的話其形態會像上面說的是一個物件,如下: 


但如果你沒有透過 new 去建構,就會被轉換為原始值,如下的型態為 string 

轉換步驟是這樣: Javascript 為了反應表達式,會先把他轉成物件,但是之後會將物件的性質都移除

並且將它改回原始值,於是當我們開發時萬一沒注意到這運作方式可能就出包了

接下來也是有關比對的問題

 

Q:原始值與複合值的比對問題

這一題也是我們平常開發容易出包的一題

請問一下輸出結果是 true or false ?

題目:
var a = "test";
var b = "test";
console.log(a === b);

答案是 true,一樣的字串相比為 true 很好理解,大家直接用腳趾頭思考就回答出來了

再看下面這題,請問輸出的結果是 true or false 

題目:
var a = { name: "eason", action: "haha" };
var b = { name: "eason", action: "haha" };
console.log(a === b);

答案是 false 

啊靠! 為何? 內容不是都完全一模模一樣樣!

這個問題的原因是,Javascript 對複合值的比對使用的是參考記憶體位址

(by value or by reference 下節討論)

因為這兩個物件在記憶體中是兩個不同的位址 (雖然它們在記憶體中儲存的值是一樣的)

而上面針對原始值是單純的比對記憶體中的"值",所以才為 true 

上面的例子把它改成用字串比就會是 true 了

那如果這樣呢 ?

題目:
var a = new String("test");
var b = new String("test");
console.log(a === b);

經過上面問題的洗禮,大家可以用肚臍回答了,答案就是 false

如果你的肚臍告訴你錯誤的答案也沒關係,請看下面的解釋

上面說過了,String 建構式如果使用 new 關鍵字的話,其形態就會是 object

而複合值在 Javascript 中比對方式是利用記憶體中的位址

a、b 在記憶體中都是各自獨立的位址,所以比對為 false

接下來看一下 Javascript 中的 by reference、by value 問題

 

Q:By Reference or By Value ?

首先程式設計師應該沒有不清楚這兩者差別的,但如果你不知道就容我解說一下

基本上會造成上面幾個問題的原因,跟 Javascript 對於原始值與複合值運作方式有很大關係

用以下例子來看比較清楚

var a = {}
var b = a;
a.foo = "haha";
 
// 請問下面輸出的 a、b 會是什麼?
console.log(a);
console.log(b);

答:a 跟 b 都是 {foo: "haha"}

這就是位址的參考

在 var a = {}; 時會給 a 在記憶體中一個位址

而 var b = a; 則代表著宣告 b 的同時,把 a 在記憶體中的位址給 b 參考了

因此 a 和 b 其實都是指向同一個記憶體位址,所以只要一改變該位址中的值,a 跟 b 都會同時被更改

再來看看傳值 By Value

var a = 3;
var b = a;
a = 4;
 
// 請問下面輸出的 a、b 會是什麼?
console.log(a);
console.log(b);

答: 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) 改變成原始值呢?

var a = new String("hello");
var b = a;
a = "你好";
console.log(a);
console.log(b);
 
輸出: 
你好
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 為何 ?

題目:
var a = 10;
function add(a) {
a += 10;
}
add(a);
console.log(a)

答案: 10

我們對 add function 傳入變數 a ,值為 10 的原始值並執行

函數內執行完後,函數外 a 的值還是 10

代表函數內的 a 跟 函數外的 a 已經是在不同的記憶體位址,所以是 By Value

那再看下一個例子

var a = { num: 10 };
function add(a) {
a.num += 10;
}
add(a);
console.log(a)

答案: { 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) 的應用

創作者介紹
創作者 小雕 的頭像
小雕

小雕雕的家

小雕 發表在 痞客邦 留言(0) 人氣()