iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 17
0

今天來看傳值與傳址

call by value傳值call by reference傳址指的是電腦記憶體中的東西,與程式的參照傳遞互動的模式。

call by value

當我們創造變數並給值時,變數會指向值在電腦記憶體中的位置,若我們以這個值為參照,指定另一個變數指向這個值時,電腦會在記憶體中新增(複製)一個新值,讓後來的這個變數指向新的值

在JavaScript裡,布林值、字串、數值、null、undefined都是call by value。

來看看以下程式碼

var a = 100;
var b;

b = a;
a = a - 70;
console.log('a現在是' + a);
console.log('b現在是' + b);
  1. 先創造一個變數a並設定進電腦記憶體
  2. 在電腦記憶體創造數值100
  3. 將變數a指向電腦記憶體內的100
  4. 再創造一個變數b並設定進電腦記憶體
  5. 電腦拷貝a的值,在電腦記憶體中設定另一個100
  6. 將變數b指向電腦記憶體內另一個100
  7. 變數a指向的值減去70

現在變數a是30,變數b是100。

挺合理的,a和b指向的值在電腦記憶體裡不一樣,當a修改時,b並不會被影響,這種特性就是call by value。


call by reference

當我們創造變數並給值(物件)時,變數會指向物件在電腦記憶體中的位置,若我們以這個物件為參照,指定另一個變數指向這物件,這個變數就會指向電腦記憶體中同樣的物件,不會有新的物件在記憶體中被創造出來。

在JavaScript裡,物件、陣列、函式都是call by reference。

來看看以下程式碼

var c = { hello : '安安' };
var d;
d = c;
c.hello = '你好';

console.log(c);
console.log(d);

如果用上面a、b的例子(call by value)來看c、d,那麼:
console.log(c)應該是顯示出{ hello : '你好' }
console.log(d)應該是顯示出{ hello : '安安' }

畢竟d和c指向的記憶體物件應該不一樣,彼此會互不影響,來看看結果

疑?兩個都一樣?

當我們給變數一個物件,其實賦予變數指向這個物件在電腦記憶體的位址,而d = c,變數d也指向同一個物件同一個該物件在記憶體的位址

c.hello = '你好'時,c指向的物件,hello屬性變成'你好',因為d指向的物件和c是同一個,d自然也是{ hello : '你好' }囉。

承接程式碼,若用函式傳參數(物件),結果也是一樣的

function changeHello(obj){
	obj.hello = '天氣真好';
}

changeHello(d); 
console.log(c.hello);
console.log(d.hello);

上面說過,物件是by reference,c、d都是指向記憶體中的同一個物件,結果自然是:

兩個都一樣

另一個例子:

var e = { hello : '安安' };
var f = { hello : '安安' };
e.hello = '哎呀';

console.log(e);
console.log(f);


疑?按照上面的說明,此時e和f應該都是{ hello : '哎呀' }才對啊?

這是因為,雖然他們的值字面上看一樣,但在電腦記憶體位置中,這兩個物件在記憶體中是獨立分開的,=運算子會建立一個新的命名空間,而且用到了物件實體語法來創造物件,是故e和f指向各自指向不同的記憶體位置,彼此自然不影響囉。


這邊再給大家看看call by value和call by reference的差別

call by value

var byValue1 = 100;
var byValue2 = byValue1;
var byValue3 = byValue1;
var byValue4 = byValue1;
console.log(byValue1,byValue2,byValue3,byValue4);

都是100

byValue1 = byValue1 - 30;
console.log(byValue1,byValue2,byValue3,byValue4);

只有byValue1的值受到了影響,這代表byValue1、byValue2、byValue3、byValue4指向的數值都是電腦記憶體中獨立分開的值,所以彼此互不影響。

call by reference

var obj1 = { ByReference : 100 };
var obj2 = obj1;
var obj3 = obj1;
var obj4 = obj1;
console.log(obj1,obj2,obj3,obj4);

現在電腦記憶體中的{ByReference:100}其實只有一個,只是同時4個變數指向它。

obj3.ByReference = obj3.ByReference - 30;
console.log(obj1,obj2,obj3,obj4);

變數obj1、obj2、obj3、obj4都指向同一記憶體內的物件,所以透過變數obj3去修改物件,全部指向它的變數都受影響。
 

最後,來加碼分享面試時有被考到的題目,當時已經知道by valueby reference的概念,但還是被陷阱坑到。

var a = { n : 1 };
var b = a;
a.x = a = { n : 2 };
console.log(a.x)
console.log(b.x)

求a.x和b.x,console分別顯示出來的內容
 
 
 
 


解析
可以分三部分來看

第一部分

  • 宣告變數a、b
  • 變數a指向{ n : 1 }
  • var b = a;,變數b也指向變數a指向的{ n : 1 }

因為by reference的關係,a、b此時都指向憶體中的同一個{ n : 1 }

第二部分

  • a.x = a = { n : 2 }

=運算子是右相依性,所以這行乍看是先從右邊看到左邊....嗎?
別忘記決定運算子順序的是優先性與相依性,可以參考MDN的運算子優先性表格

.運算子的優先性高於=運算子

所以先看最左邊的a.x,但因為a物件{ n : 1 }並沒有x屬性的存在,
於是就創造x這個屬性,a.x是undefined

a、b此時共同指向的物件變成{ n : 1 , x : undefined }

然後才因為=運算子,由右看到左。

  • a = { n : 2 }

a改指向記憶體中的新物件{n:2},因為by reference的關係,此時b仍指向記憶體中的物件
{ n : 1 , x : undefined }

  • a.x = a

將a現在指向的物件{ n : 2 }賦予給a.x,這邊的a.x其實是先前與b一同指向的
{ n : 1 , x : undefined }的x屬性,也就是undefined
此時{ n : 1 , x : undefined }這個物件變成{ n : 1 , x : { n : 2 } }

現在的狀況
a指向{ n : 2 }
b指向{ n : 1 , x : { n : 2 } }

第三部分

  • console.log(a.x)
    這個時候的a指向{n : 2 },console.log卻是要看a.x{ n : 2 }並沒有屬性x,於是創造x屬性,a物件變成是{n : 2 , x : undefined }a.x就是undefined囉!

  • console.log(b.x)
    這個時候的b指向{ n : 1 , x : { n : 2 } }b.x自然是{ n : 2 }

瀏覽器console,看看結果:

 
 
 
小結
JS同時具有call by valuecall by reference的特性,這種傳遞特性稱作call by sharing。理解這種特性是很重要的,畢竟面試會考它是物件導向語言啊,瞭解JS物件、函式的call by reference,可以幫助我們避掉一些開發上的bug。

今天的筆記內容可以參照Udemy課程:JavaScript 全攻略:克服JS 的奇怪部分4-36


上一篇
Day16 函式陳述句與函式表示式
下一篇
Day18 物件、函式與「this」
系列文
JavaScript基礎二三事30

尚未有邦友留言

立即登入留言