js庫 - 淺拷貝 & 深拷貝

學了堆棧內存空間,應該就理解了什麼叫簡單數據類型存在棧內存,複雜數據類型存在堆內存了。javascript

而後面試中常常會問、業務中也常常會遇到的問題就是深淺拷貝的問題了。html

棧內存中簡單數據類型直接拷貝就能獲得一個副本,可是複雜數據類型的拷貝若是也想獲得一個副本,就須要深拷貝了。java

 

淺拷貝:

var  a = 1;

var b = a;

 

這就是淺拷貝了,雖然你視覺上看上去a = b;可是修改b的值,a不會收影響。由於b是a的一個副本,就像你拷貝了一個文件夾副本同樣。修改副本,源文件夾不會受影響。面試

但這種拷貝狀況只侷限在簡單類型的拷貝:數組

string、number、boolean、null、undefiend數據結構

若是你拷貝一個數組/對象(以數組爲例):函數

var c = [1,2,3];

var d = c;

d.push(4);

console.log(c)

 

c也被改變了。this

可是若是你改變d的整個數據,讓他等於一個新的數組(甚至對象)c這時返倒不受影響了。spa

很奇怪很費解吧?prototype

這是由於:

數組、對象這類複雜類型數據結構,在棧內存裏存放的只是指向堆內存中存放數據的地址,

你直接d = c; 拷貝的也是一個副本,但這個副本區別之處是,他並不是數據的副本,而是棧內存地址的副本。

這個副本d的地址改變,對c沒有影響,而你給d從新指向一個新的數組,就是改變地址了。此時只有d指向了新數組[5,6,7],可是c不受影響,因此打印出來的不變。

 

可是雖然有兩個地址,一個是原、一個是副本。可他們同時指向同一個堆內存的數據。

這樣看來,你拷貝出來的d和c用的是同一個數組。

因此d.push執行之後,並非c也跟着push了,而是c指向的數組和被d.push的是同一個數組。

你能夠想象成,同一個房間,d和c都拿着鑰匙,這倆鑰匙是不一樣的,可是他們都是同一間屋子的鑰匙。d用鑰匙打開門往屋子裏放了一個東西。那麼c打開這個屋子,這個東西也還會在。

 

以上這些,就是基礎知識的解讀。

 

深拷貝

具體深拷貝就是要理解了複雜類型拷貝的缺點,而後再進行彌補。

既然想要複雜類型也像簡單類型那樣拷貝一個新數據的話,就不僅僅是拷貝地址了。而是要從新創建一個新數據存放空間,而後挨個把想要拷貝的東西搬進來才行。

 

因此拷貝前咱們得判斷一個數據他究竟是什麼類型的,好「對症下藥」:

getType: function (target) {
  /*
    * @Author: guojufeng@ 
    * @Date: 2017-12-20 15:07:06
    * @purpose 獲取一個值的類型
    * @param {variateName} target: 要獲取類型的變量名或對象
    * @output {string} result || "null": 返回的參數 - result或者null,爲字符串形式的
    */
  if (target === null) {
    console.log("getType類型判斷爲: " + target)
    return "null";
  }
  let result = typeof (target);
  if (result == "object") {
    if (target instanceof Array) {
      result = "array";
    } else if (target instanceof Object) {
      let target = Object.prototype.toString.call(target);
      if (target == "[object Object]") {
        result = "object";
      } else {
        result = target;//構造類
      }
    }
  }
  console.log("getType類型判斷爲: " + result)
  return result; //返回類型值的字符串形式
}

以上代碼,判斷一個值的類型,輸入值自己,返回字符串形式的類型描述。

若是是簡單類型,返回typeof方法返回的對應值便可。

這裏特殊處理了null,由於他用typeof返回object。

而後對於複雜類型的數據,再深刻判斷其實array類型仍是object類型。

對object類型中,還有構造類須要區分。直接返回[object String]這樣類型的。但其實在深拷貝階段,直接將其放到object形式處理了。

 

 

而後就是深拷貝的代碼:

deepClone: function (origin) {
  /*
    * @Author: guojufeng@ 
    * @Date: 2018-10-30 20:48:44 
    * @purpose 深度克隆
    * @param {variateName} origin: 要克隆的對象變量名
    * @output {對應值} 根據origin的類型返回的具體值
    */
  let type = this.getType(origin),
    target;
  if (type == "array") {
    target = [];
    /* 數組 */
    origin.forEach(el => {
      // console.log("ele",el)
      target.push(this.deepClone(el));
    });
  } else if (type == "object") {
    /* 對象 */
    target = {};
    for (const key in origin) {
      if (origin.hasOwnProperty(key)) {
        /* 注意,只拷貝元素身上的,而不拷貝其原型上的值 */
        const el = origin[key];
        target[key] = this.deepClone(el);
      }
    }
  } else if (type == "function") {
    /* 函數 */
    target = function () {};
    target = origin;
  } else {
    /* 原始值 */
    target = origin;
  }
  return target;
}

  

對於簡單類型,直接進行拷貝

對於函數,新建一個function,而後拷貝

對於數組,新建一個數組,而後 forEach 遍歷拷貝。若是循環過程當中,數組中嵌套複雜類型,再次遞歸調用深拷貝方法。

對於對象,新建一個對象,而後for in遍歷拷貝非原型值。若是循環過程當中,對象中嵌套複雜類型,再次遞歸調用深拷貝方法。

以上,就是整個邏輯。

 

 

 

 

 

聲明:

  請尊重博客園原創精神,轉載或使用圖片請註明:

  博主:xing.org1^

  出處:http://www.cnblogs.com/padding1015/

相關文章
相關標籤/搜索