晨叔技術晨報: 你真的搞懂JS中的「值傳遞」和「引用傳遞」嗎?

 晨叔週刊,每週一話題,技術每天漲。 前端

 

  本週的話題是JS的內存問題(加入本週話題,請點擊傳送門)。git

 

圖 話題入口微信

 

  今天的技術晨報來,就來談談JS中變量的,值傳遞和引用傳遞的問題。如今,對於不少的JSer來說,基本不關心堆和棧的問題,代碼照樣666。函數

可是,如今的前端,再也不是傳統的JQ時代,而是MVVM,組件化,工程化。前端的承載着複雜業務邏輯。爲此,內存問題,成爲JSer必需要考慮的問題。 本文從堆棧講起,讓你們理解JS中變量的內存使用以及變更狀況 。組件化

 

1、初步瞭解堆棧 spa

先初步瞭解JS中的堆和棧,首先,內存空間分爲 堆和棧兩個區域,js 代碼運行時,js解析器會先判斷變量類型,根據變量類型,將變量放到不一樣的內存空間中(堆和棧)。指針

 圖 1code

 

 基本的數據類型(String,Number,Boolean,Null,Undefined)都會分配棧區。而Object (對象)類型的變量都放到堆區。對象

 以下代碼示例blog

1 var a = 12;
2 var b = false;
3 var c  = "string"
4 
5 var chenshu =  {name:"晨叔週刊",desc:"每週一話題,技術每天漲" }

 

對應的內存分配圖以下圖2

 

圖 2 

 

棧區的特色:空間小,數據類型簡單,讀寫速度快,通常由JS引擎自動釋放

堆區的特色:空間大,數據類型複雜,讀寫速度稍遜,當對象不在被引用時,纔會被週期性的回收。

 

瞭解了內存的棧區和堆區後, 接下來,來看看變量如何在棧區和堆區「愉快的玩耍」。

 

2、變量傳遞 

 進入今天的重點,先看看下面的代碼。

var a = 12;
var b= a; 
b = 13;

 上面代碼的運行結果,a 變量的值沒變,由於 第二行「b = a」 ,把a的值賦值個b時, 執行的是「複製」的操做,a 和 b 沒有關係。(so easy ,不在多BB),再往下看

1 var chenshu =  {name:"晨叔週刊",desc:"每週一話題,技術每天漲" };
2 var xiaoming = chenshu;
chenshu 不是基礎類型變量, 而是一個對象。

第二行中,「xiaoming = chenshu」,進行也是「複製的操做」, 爲何? 且看下圖分解。

根據代碼,首先, 申明瞭變量「chenshu」 ,以下圖3。

 

圖 3 

 

接下來, 「xiaoming = chenshu」(複製操做),內存空間圖以下圖4

 

 

 圖4

 

在上圖4 中,xiaoming 變量的存儲空間(棧區)複製了 chenshu變量的值,可是這個變量的值,並非一個基礎的數據類型,而是一個堆區的內存地址(指針)。因此操做「xiaoming」變量和操做「chenshu」變量效果都同樣。

劃重點:在JS的變量傳遞中,本質上均可以當作是值傳遞,只是這個值多是基礎數據類型,也多是一個指針,若是是指針,咱們一般就說爲引用傳遞。 JS中比較特殊,不能直接操做對象的內存空間,必須經過指針(所謂的引用)來訪問。

 

因此,即便是因此複雜數據類型(對象)的賦值操做,本質上也是值傳遞。在往下看看一個例子。 

 

3、函數的形參、實參的值傳遞 


看以下代碼。

function setName(user)
{
      user.name = "new name";// 從新設置name 這個屬性
}

var chenshu = { name:"晨叔" };

setName(chenshu);

console.log(chenshu.name);

 講解: 

  函數 setName 有一個 形參 「user」,沒毛病。

  將「chenshu」對象傳給 「setName 」 函數,chenshu 爲實參,也沒毛病。

重點:傳參的過程當中,js引擎將實參chenshu的值(值是對象{name:"晨叔"}的指針)「複製」給形參。即,形參user 和 chenshu變量指向同一個堆內存對象。 沒毛病。 但問題來了,實參和形參在是一個變量?仍是兩個變量? 不少開發者的誤區:認爲 在 setName 函數中改變了形參user的屬性,實參 chenshu的屬性也發生變更,就認爲同一個變量,但真實的狀況是:實際上實參和形參 是兩個變量,只是實參和形參指向同一個堆區的變量而已,見以下圖5

 圖5 

 

形參user只是存了對象 { name:"晨叔" }的地址,但在棧區中,user是單獨存在的,並且函數運行完,user 當即被釋放了,爲了更加直觀的說明這個問題。咱們再來分析一段代碼。

 1  function setName(user)
 2 {
 3       user.name = "new name";// 從新設置name 這個屬性
 4       user = { name:"西門吹雪" };
 5  }
 6 
 7  var chenshu = { name:"晨叔" };
 8  setName(chenshu);
 9 
10  console.log(chenshu.name);//輸出  new name

 

上面這段代碼就能很能說明問題,在「setName」函數中,形參user首先修改實參chenshu對象的name屬性以後, 又從新指向了一個新對象「{ name:"西門吹雪" }」, 但由於 形參user和實參chenshu是兩個對象,形參user指向新對象後,對實參chenshu並無影響,由於實參和形參進行的是值傳遞(複製),實參和形參是兩個獨立在棧區的變量。 

 

本文的分享就到這裏,咱們來作一下總結。

  1.  咱們初步理解了js中的堆區和棧區。
  2.  理解了變量傳遞的方式——值傳遞,所謂的引用傳遞,本質上也是值傳遞,只是傳遞的這個值是一個指針, 值傳遞帶來的效果就是在內存棧區建立了一個新的空間。
  3.  經過函數的實參和形參傳遞的分析,咱們進一步的理解js的值傳遞。 

本文的內容只是初步的探索JS的內存問題,更深的乾貨,更精彩的內容,請加入本期的分享話題,點擊傳送門

 請關注「晨叔週刊」微信公衆號, 便可每週與晨叔深刻研究一個話題,每週一發布本週話題,週二,週六 早上九點更新週刊內容。晨叔口號: 晨叔週刊,每週一話題,技術每天漲。

相關文章
相關標籤/搜索