從賦值看基本型別和引用型別的區別

大眾美男典範發表於2018-07-27

賦值就是把某一個值賦給變數。

我憑什麼要把值賦給變數?

變數相當於名字。

拿我舉例,如果我沒有名字,當別人叫我幫忙的時候,就只能說:

“那個個頭不高、顏值爆表、頭髮很硬、坐在角落的小哥哥,過來幫我一下唄!”

而有名字的情況是:

“小強快來!”

可見變數賦值的意義在於便於使喚

基本型別的賦值

基本型別的賦值,好比在每個盒子裡放東西。

直接賦值

例如,

var a = '手機'
console.log(a)  // '手機'
複製程式碼

相當於,給一個盒子起名為a,並放進去一個字串‘手機’。

將變數賦值給變數

例如,

var a = '蘋果'
var b = a
a = ''
console.log(a)  // '' 
console.log(b)  // '蘋果' 
複製程式碼

var b = a時,相當於:

給一個盒子起名為b,並偷看一下盒子a裡面放的什麼,然後自己裡面放同樣的東西。

從賦值看基本型別和引用型別的區別

a = ''時,相當於:

盒子a裡面原來的東西不要了,改放一個''進去。

但是,這並不會影響盒子b中的值。

因為,在盒子a裡面發生變化的時候,盒子b並不關心。

只有將變數a賦值給變數b,即b = a時,

b才會出現偷看行為,進而使自身的值和a的值一樣。

可見,賦值就是複製,特點是賦值過後互不影響

好比我把感冒複製給朋友,我吃完藥好了,並不能代表他也好了。

引用型別的賦值

引用型別的賦值,看上去是共享,實際還是複製。

棧和堆

首先,基本型別採用棧儲存,一個蘿蔔一個坑。

前面也說到,基本型別的賦值,就像在每個盒子裡放東西,比如,

我在a盒子裡放個香蕉、

我在b盒子裡放個手機、

我在c盒子裡放個老鼠、

我在d盒子裡放個房子...

從賦值看基本型別和引用型別的區別

問題出現了,

之前,我們在盒子裡放一些很傻很天真的東西,基本沒問題,

但是,盒子裡可以放房子嗎?

這時候,我們進一步認識一下我們的盒子(棧儲存)有什麼特點?

  • 盒子按順序排列
  • 每個盒子只能存放一件物品
  • 每個盒子都不是很大

顯然,我們的盒子不足以裝下房子。

而且常識告訴你:

房子裡倒是能很多的盒子,因此房子就是堆儲存。

從賦值看基本型別和引用型別的區別

堆儲存就像找了一片空地,然後在上面盡情放盒子(請不要想到《我的世界》)。

特點就是存的多、存的亂。

理解堆和棧的區別,也可以參照公交車,

每個座位,

有序、只能坐一人、大小有限...

好比棧;

其它地方,

能擠下一堆人,很亂...

好比堆。

物件採用的就是堆儲存。

什麼是引用型別

我們知道,javascript引用型別包括物件。

先隨便來一個物件:

var a = {}  // 堆儲存,相當於建了個房子,名為a
複製程式碼

再隨便來一個數字:

var b = 10  // 棧儲存,相當於選了個盒子,名為b
複製程式碼

再隨便來一下:

b = a
console.log(b)  // {}
複製程式碼

太隨便就容易出問題:

原本b是一個正經盒子,a是一個正經房子,

執行b = a之後,b變成了{}

根據上面說的基本型別的賦值,這是不是在說

盒子b雖然之前只是一個小盒子、

但是偷看了一眼房子a之後、

自己發奮圖強變成像房子a一樣大的盒子、

並複製了房子a裡面的東西?

不是的。

盒子b並沒有為了複製房子a裡面的東西而變大,

實際上,a其實從頭到尾都只是一個盒子、而不是房子,僅管我們看到是a被賦值了一個堆儲存的物件。

為什麼呢?

因為引用型別只暴露一個地址給我們,

操作引用型別的資料時(比如賦值),我們也只是在操作這個地址、引用、指標...愛叫啥叫啥。

這就好比,你送女朋友戒指,可以直接放到她手裡,這是基本型別;

你送女朋友法拉利,並非把法拉利直接放到她手裡,而是把車鑰匙放到她手裡,這就是引用型別;

你送女朋友房子,並非把房子直接交給她(除非你們是蝸牛),而是把房鑰匙交給她,這也是引用型別。

從賦值上來進一步認識引用型別:

直接賦值

開始說過,

變數相當於名字。

另一個事實是,

我們只能給盒子起名字,不能給房子起名字,但是我們能拿到房子的鑰匙

這就是為什麼說,a其實從頭到尾都只是一個盒子。

var b = 10相當於取一個盒子叫b,然後裡面放啥啥啥,這沒有問題;

由於我們不能給房子起名字,

所以var a = {}肯定不是說:取了一個房子叫a,然後裡面放啥啥啥。

其實,和var b = 10的解釋一模一樣,var a = {}也相當於

取了一個盒子叫a,然後裡面放啥啥啥。

只不過是,b盒子裡面放很傻很天真的東西,

a盒子裡面放很傻很天真的鑰匙,這把鑰匙對應一個大房子。

引用型別的直接賦值就是把鑰匙放到對應盒子裡。

從賦值看基本型別和引用型別的區別

為什麼只給盒子起名字?

程式碼中,會出現很頻繁的變數賦值行為,

為了保證執行速度,這些行為被優先安排在一批有序的盒子中,偷看、複製、再偷看...

可以說,我們大部分時間在玩盒子。

可想而知,如果換成玩房子的話,要費多大的力氣。

但是呢,房子在我們的程式中也有著不可或缺的作用,

這時候它就暴露出一個可以找到它的鑰匙,相當於它的聯絡方式

然後放進相應的盒子裡,並說:

當你需要我的時候,我會在你身邊。

正是因為這樣,我們既便於使喚房子,又便於操作房子裡的東西。

將變數賦值給變數

var obj = {name: '小強'}
var obj2 = obj
console.log(obj2)   // {name: '小強'}
複製程式碼

首先,var obj = {name: '小強'}是引用型別的直接賦值,

相當於找到一個盒子名obj,把{name: '小強'}這個房子的鑰匙放進盒子obj裡面。

obj2 = obj可以說和基本型別的變數賦值給變數一樣,

盒子obj2偷看一眼盒子obj中放的東西,複製一下,自己裡面放同樣的東西。

從賦值看基本型別和引用型別的區別

喜出望外的是,竟然是一把對應某個房間的鑰匙!

這時,obj2就和obj一樣,都能訪問這把鑰匙對應的房間了。

所以引用物件的賦值都是操作鑰匙。

插播廣告: {name: '小強'} == {name: '小強'}嗎?

答案是否定的。

這相當於在問,這兩個房子的鑰匙相同嗎?

這兩個房子只是在裝修上極其相似,我們不能通過將自己的房子佈置得和鄰居的房子一樣、就能得到鄰居家的房鑰匙,

程式也是如此。

引用型別賦值面試題

例一、

var a = {n: 1}
var b = a
a.x = a = {n: 2}
console.log(a.x)    // undefined
console.log(b.x)    // {n: 2}
複製程式碼

逐句翻譯吧:

  1. var a = {n: 1}

取一個盒子名a,建一個房子,鑰匙放到盒子a裡面;

房子裡有個盒子n,放著1。

從賦值看基本型別和引用型別的區別

  1. var b = a

取一個盒子名b,盒子b偷看一下盒子a,哇哦,一把鑰匙,

盒子b裡面也有了這把鑰匙,也能去訪問這個房間了。

從賦值看基本型別和引用型別的區別

  1. a.x = a = {n: 2}
  • 變數賦值是從右向左的
  • 物件用.賦值的時候,就是操作物件的某個屬性,如果沒有該屬性就新增一個

我們通過盒子a中的鑰匙,來到了這把鑰匙對應的房間,

然後,我們在這個房間取一個盒子名x,並企圖在裡面放東西。

執行到a.x = a的時候,我們還以為:

是把盒子a裡面的鑰匙,放進我們所處房間的盒子X裡面嗎?

差點就是了,但是後面又有=賦值。

根據變數賦值從右向左,

我們暫時先不在這個房間裡的盒子x放東西,而是優先執行a = {n: 2}

這條語句顯然是引用型別的直接賦值,

即建了一個是{n: 2}這種樣子的房子,然後把鑰匙放到盒子a裡面。

在棧和堆裡面我們提到過:

每個盒子只能存放一件物品。

因此,盒子a首先會拋掉之前的鑰匙,然後存下這把新的鑰匙。

剛才我們拿著盒子a之前的鑰匙,進到對應的房間,企圖在房間的盒子x裡放東西;

然後,發現後面還有賦值行為,所以優先執行後面的賦值行為。

但是,當時我們只是暫停,而不是放棄。

換句話說,是不忘初心,有始有終。

當初我們進的哪個房子,想在哪個盒子放東西,

現在我們就回到哪個房子,然後給哪個盒子放東西,

a.x = a可以看出,我們在盒子x裡放的是盒子a的鑰匙,

在這個例子中,盒子a中現在的鑰匙就是能開啟{n: 2}這間房子的鑰匙。

雖然說,

變數賦值是從右向左的。

但是,程式碼執行是從左向右的

無論後面發生了多大變化,a.x都是最先執行的,它的作用就是:

通過鑰匙來到一個房間,取盒子x,然後等著在裡面放東西。

後面的程式碼,只能影響這個盒子裡放什麼東西。

從賦值看基本型別和引用型別的區別

於是,時過境遷:

盒子a裡,拋棄舊房子鑰匙,放進了一把新房子鑰匙,等價於

a = {n: 2}
複製程式碼

盒子b裡,還是舊房子的鑰匙。

同時,因為在盒子a換鑰匙之前,我們通過盒子a拿到舊鑰匙來到舊房子,

並將盒子a換鑰匙之後的新鑰匙,放進了舊房子的盒子x裡面,那盒子b等價於

b = {
    n: 1,
    x: {n: 2}
}
複製程式碼

也可以將這個例子稍加處理:

var xiaoMing = {moneyBox: 1}
var xiaoQiang = xiaoMing
xiaoMing.keyBox = xiaoMing = {moneyBox: 200}
console.log(xiaoMing.keyBox)    // undefined
console.log(xiaoQiang.keyBox)    // {moneyBox: 200}
複製程式碼

再逐句翻譯:

  1. var xiaoMing = {moneyBox: 1}

小明有一把房鑰匙,這個房子裡有個錢櫃,裡面放著1元錢。

  1. var xiaoQiang = xiaoMing

小強偷偷複製了一把小明的房鑰匙,從此他也可以進出小明的房子。

  1. xiaoMing.keyBox = xiaoMing = {moneyBox: 200}

小明在此房子裡做了一個鑰匙櫃,這個鑰匙櫃能自動生成一把小明口袋裡的鑰匙(xiaoMing.keyBox = xiaoMing的作用,可能有點超現實),

從賦值看基本型別和引用型別的區別

但是小明想,我口袋裡的鑰匙現在就是這個房子的鑰匙,放在我的鑰匙櫃裡也沒什麼意義,

不如這樣吧,我再買一套房子,把口袋裡的鑰匙替換成新房子的鑰匙,那這個鑰匙櫃裡不就存下新房子的鑰匙了嗎。

於是,小明果斷又買了一套房子,這個房子裡也有個錢櫃,裡面放200元錢。

小明正準備回舊房子呢,突然想起來,自己口袋裡的鑰匙已經替換成新房子的鑰匙了,

現在他只能進新房子,而進不去舊房子了,鬱悶...

再說小強,

小強當初複製的是小明舊房子的鑰匙,所以小強依然能來到這個舊房子,

進來後發現,多了一個鑰匙櫃,並且裡面放著一把鑰匙,

沒錯,這就是小明新房子的鑰匙。

所以現在的局勢很明朗了:

小明只有新房子的鑰匙,只能進新房子(而且他應該覺得舊房子已經沒人能進去了)。

而小強有小明舊房子的鑰匙,

同時這個房間裡還有小明的新房子的鑰匙,所以小強也能進小明的新房子。

從賦值看基本型別和引用型別的區別

用程式碼表示,就相當於

xiaoMing = {moneyBox: 200}
xiaoQiang = {
    moneyBox: 1,
    keyBox: {moneyBox: 200}
}
複製程式碼

感謝大家指出文章中的諸多bug,

點選檢視此文章的最新版本和相關內容。

相關文章