亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

javascript深拷貝、淺拷貝和循環引用深入理解

2019-11-19 13:46:24
字體:
來源:轉載
供稿:網友

一、為什么有深拷貝和淺拷貝?

這個要從js中的數據類型說起,js中數據類型分為基本數據類型和引用數據類型。

基本類型值指的是那些保存在棧內存中的簡單數據段,即這種值是完全保存在內存中的一個位置。包含Number,String,Boolean,Null,Undefined ,Symbol。

引用類型值指的是那些保存在堆內存中的對象,所以引用類型的值保存的是一個指針,這個指針指向存儲在堆中的一個對象。除了上面的 6 種基本數據類型外,剩下的就是引用類型了,統稱為 Object 類型。細分的話,有:Object 類型、Array 類型、Date 類型、RegExp 類型、Function 類型 等。

正因為引用類型的這種機制, 當我們從一個變量向另一個變量復制引用類型的值時,實際上是將這個引用類型在棧內存中的引用地址復制了一份給新的變量,其實就是一個指針。因此當操作結束后,這兩個變量實際上指向的是同一個在堆內存中的對象,改變其中任意一個對象,另一個對象也會跟著改變。

因此深拷貝和淺拷貝只發生在引用類型中。簡單來說他們的區別在于:

1. 層次

  • 淺拷貝 只會將對象的各個屬性進行依次復制,并不會進行遞歸復制,也就是說只會賦值目標對象的第一層屬性。
  • 深拷貝不同于淺拷貝,它不只拷貝目標對象的第一層屬性,而是遞歸拷貝目標對象的所有屬性。

2. 是否開辟新的棧

  • 淺拷貝 對于目標對象第一層為基本數據類型的數據,就是直接賦值,即「傳值」;而對于目標對象第一層為引用數據類型的數據,就是直接賦存于棧內存中的堆內存地址,即「傳址」,并沒有開辟新的棧,也就是復制的結果是兩個對象指向同一個地址,修改其中一個對象的屬性,則另一個對象的屬性也會改變,
  • 深拷貝 而深復制則是開辟新的棧,兩個對象對應兩個不同的地址,修改一個對象的屬性,不會改變另一個對象的屬性。

二、淺拷貝

以下是實現淺拷貝的幾種實現方式:

1.Array.concat()

  const arr = [1,2,3,4,[5,6]];  const copy = arr.concat(); // 利用concat()創建arr的副本    //改變基本類型值,不會改變原數組  copy[0] = 2;   arr; //[1,2,3,4,[5,6]];  //改變數組中的引用類型值,原數組也會跟著改變  copy[4][1] = 7;  arr; //[1,2,3,4,[5,7]];  

能實現類似效果的還有slice()和Array.from()等,大家可以自己嘗試一下~

2.Object.assign()

const obj1 = {x: 1, y: 2};const obj2 = Object.assign({}, obj1);obj2.x = 2; //修改obj2.x,改變對象中的基本類型值console.log(obj1) //{x: 1, y: 2} //原對象未改變console.log(obj2) //{x: 2, y: 2}
const obj1 = {  x: 1,   y: {    m: 1  }};const obj2 = Object.assign({}, obj1);obj2.y.m = 2; //修改obj2.y.m,改變對象中的引用類型值console.log(obj1) //{x: 1, y: {m: 2}} 原對象也被改變console.log(obj2) //{x: 2, y: {m: 2}}

三、深拷貝

1.JSON.parse()和JSON.stringify()

const obj1 = {  x: 1,   y: {    m: 1  }};const obj2 = JSON.parse(JSON.stringify(obj1));console.log(obj1) //{x: 1, y: {m: 1}}console.log(obj2) //{x: 1, y: {m: 1}}obj2.y.m = 2; //修改obj2.y.mconsole.log(obj1) //{x: 1, y: {m: 1}} 原對象未改變console.log(obj2) //{x: 2, y: {m: 2}}

這種方法使用較為簡單,可以滿足基本日常的深拷貝需求,而且能夠處理JSON格式能表示的所有數據類型,但是有以下幾個缺點:

  • undefined、任意的函數、正則表達式類型以及 symbol 值,在序列化過程中會被忽略(出現在非數組對象的屬性值中時)或者被轉換成 null(出現在數組中時);
  • 它會拋棄對象的constructor。也就是深拷貝之后,不管這個對象原來的構造函數是什么,在深拷貝之后都會變成Object;
  • 如果對象中存在循環引用的情況無法正確處理。

2.遞歸

function deepCopy1(obj) {  // 創建一個新對象  let result = {}  let keys = Object.keys(obj),    key = null,    temp = null;  for (let i = 0; i < keys.length; i++) {    key = keys[i];      temp = obj[key];    // 如果字段的值也是一個對象則遞歸操作    if (temp && typeof temp === 'object') {      result[key] = deepCopy(temp);    } else {    // 否則直接賦值給新對象      result[key] = temp;    }  }  return result;}const obj1 = {  x: {    m: 1  },  y: undefined,  z: function add(z1, z2) {    return z1 + z2  },  a: Symbol("foo")};const obj2 = deepCopy1(obj1);obj2.x.m = 2;console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}

四、循環引用

看似遞歸已經完全解決我們的問題了,然而還有一種情況我們沒考慮到,那就是循環引用

1.父級引用

這里的父級引用指的是,當對象的某個屬性,正是這個對象本身,此時我們如果進行深拷貝,可能會在子元素->父對象->子元素...這個循環中一直進行,導致棧溢出。比如下面這個例子:

 const obj1 = {  x: 1,   y: 2};obj1.z = obj1;const obj2 = deepCopy1(obj1); //棧溢出

解決辦法是:只需要判斷一個對象的字段是否引用了這個對象或這個對象的任意父級即可,可以修改上面的deepCopy函數:

function deepCopy2(obj, parent=null) {  //創建一個新對象  let result = {};  let keys = Object.keys(obj),     key = null,     temp = null,     _parent = parent;  //該字段有父級則需要追溯該字段的父級  while(_parent) {    //如果該字段引用了它的父級,則為循環引用    if(_parent.originParent === obj) {      //循環引用返回同級的新對象      return _parent.currentParent;    }    _parent = _parent.parent  }  for(let i=0,len=keys.length;i<len;i++) {    key = keys[i]    temp = obj[key]    // 如果字段的值也是一個新對象    if(temp && typeof temp === 'object') {      result[key] = deepCopy(temp, {        //遞歸執行深拷貝,將同級的待拷貝對象與新對象傳遞給parent,方便追溯循環引用        originParent: obj,        currentParent: result,        parent: parent      });    } else {      result[key] = temp;    }  }  return result;}const obj1 = {  x:1}obj1.z = obj1;const obj2 = deepCopy2(obj1);

2. 同級引用

假設對象obj有a,b,c三個子對象,其中子對象c中有個屬性d引用了對象obj下面的子對象a。

const obj= {  a: {    name: 'a'  },  b: {    name: 'b'  },  c: {  }};c.d.e = obj.a;

此時c.d.e和obj.a 是相等的,因為它們引用的是同一個對象

console.log(c.d.e === obj.a); //true

如果我們調用上面的deepCopy2函數

const copy = deepCopy2(obj);console.log(copy.a); // 輸出: {name: "a"}console.log(copy.d.e);// 輸出: {name: "a"}console.log(copy.a === copy.d.e); // 輸出: false

以上表現我們就可以看出,雖然opy.a 和copy.d.e在字面意義上是相等的,但二者并不是引用的同一個對象,這點上來看對象copy和原對象obj還是有差異的。

這種情況是因為obj.a并不在obj.d.e的父級對象鏈上,所以deepCopy2函數就無法檢測到obj.d.e對obj.a也是一種引用關系,所以deepCopy2函數就將obj.a深拷貝的結果賦值給了copy.d.e。

解決方案:父級的引用是一種引用,非父級的引用也是一種引用,那么只要記錄下對象A中的所有對象,并與新創建的對象一一對應即可。

function deepCopy3(obj) {  // hash表,記錄所有的對象的引用關系  let map = new WeakMap();  function dp(obj) {    let result = null;    let keys = Object.keys(obj);    let key = null,      temp = null,      existobj = null;    existobj = map.get(obj);    //如果這個對象已經被記錄則直接返回    if(existobj) {      return existobj;    }    result = {}    map.set(obj, result);    for(let i =0,len=keys.length;i<len;i++) {      key = keys[i];      temp = obj[key];      if(temp && typeof temp === 'object') {        result[key] = dp(temp);      }else {        result[key] = temp;      }    }    return result;  }  return dp(obj);}const obj= {  a: {    name: 'a'  },  b: {    name: 'b'  },  c: {  }};c.d.e = obj.a;const copy = deepCopy3(obj);

五、總結

其實拷貝的方式還有很多種,比如jquery中的$.extend,lodash的_.cloneDeep等等,關于拷貝中還有很多問題值得深究,比如正則類型的值如何拷貝,原型上的屬性如何拷貝,這些我都會慢慢研究噠!大家也可以思考一下~
最后,歡迎點贊和收藏!!錯誤之處歡迎指正(`・ω・´)

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产主播精品在线| 国产va免费精品高清在线观看| 成人黄色av免费在线观看| 国产一区二区在线播放| 久久免费精品视频| 亚洲色图国产精品| 最近日韩中文字幕中文| 亚洲成**性毛茸茸| 少妇高潮久久77777| 欧美午夜www高清视频| 97色在线视频观看| 成人h片在线播放免费网站| 亚洲天堂av网| 九九久久综合网站| 国语对白做受69| 欧美成人精品h版在线观看| 中文字幕精品—区二区| 国产精品美女av| 亚洲国产私拍精品国模在线观看| 久久婷婷国产麻豆91天堂| 亚洲一区中文字幕| 亚洲国产高潮在线观看| 欧美性猛交xxxxx免费看| 91视频国产精品| 九色成人免费视频| 久久久999精品免费| 日韩高清欧美高清| 懂色aⅴ精品一区二区三区蜜月| 亚洲乱码一区av黑人高潮| 精品国产91乱高清在线观看| 久久精品国产亚洲| 亚洲精品国产精品乱码不99按摩| 亚洲视频欧洲视频| 欧美精品18videos性欧美| 国模私拍一区二区三区| 亚洲欧美999| 国产精品白丝jk喷水视频一区| 国产亚洲日本欧美韩国| 国产亚洲精品久久| 日韩精品在线电影| 亚洲男人的天堂网站| 午夜精品一区二区三区视频免费看| 欧美理论电影在线播放| 国产精品日韩在线观看| 亚洲第一黄色网| 国产成人亚洲综合91| 日韩欧美国产骚| 国产69精品久久久久9| 久久精品青青大伊人av| 亚洲人成77777在线观看网| 奇米4444一区二区三区| 国产精品h在线观看| 国产日本欧美一区二区三区| 成人激情视频网| 91精品国产自产在线观看永久| 日韩中文字幕在线精品| 国产精品高精视频免费| 亚洲免费视频在线观看| 亚洲石原莉奈一区二区在线观看| 亚洲激情在线观看| 精品国产1区2区| 欧美二区在线播放| 日本免费久久高清视频| 亚洲黄在线观看| 欧美日韩在线观看视频小说| 91成人在线视频| 欧美中文字幕在线播放| 久久久久久91香蕉国产| 91精品免费视频| 久久久最新网址| 亚洲精品国精品久久99热| 日韩av电影国产| 国产一区二区三区视频免费| 国产精品视频免费在线观看| 日韩黄色高清视频| 亚洲国产精品一区二区久| 欧美制服第一页| 69久久夜色精品国产69| 欧美最猛性xxxxx(亚洲精品)| 亚洲va久久久噜噜噜久久天堂| 亚洲精品成a人在线观看| 欧美成人中文字幕在线| 色一情一乱一区二区| 欧美大片网站在线观看| 成人性生交大片免费观看嘿嘿视频| 这里只有精品在线观看| 最近的2019中文字幕免费一页| 大荫蒂欧美视频另类xxxx| 国产精品美女久久久免费| 日日摸夜夜添一区| 最近2019年好看中文字幕视频| 亚洲国产精品久久久久秋霞蜜臀| 久久久久久国产精品三级玉女聊斋| 在线亚洲午夜片av大片| 亚洲天堂成人在线视频| 色综合久久中文字幕综合网小说| 中文欧美在线视频| 国产精品视频26uuu| 久久全国免费视频| 欧美日韩高清区| 欧美一级高清免费播放| 亚洲欧美成人在线| 国产一区私人高清影院| 宅男66日本亚洲欧美视频| 欧美成人中文字幕在线| 久久精品视频导航| 色综合视频一区中文字幕| 91麻豆桃色免费看| 日韩电视剧在线观看免费网站| 国产欧美精品日韩| 中文在线资源观看视频网站免费不卡| 国产视频精品一区二区三区| 日韩国产精品亚洲а∨天堂免| 日韩中文在线观看| 亚洲国产日韩欧美在线图片| 国产精品91视频| 日韩精品中文字幕久久臀| 欧美成人三级视频网站| 亚洲深夜福利在线| 午夜精品一区二区三区在线视频| 欧美精品久久久久久久免费观看| 日韩欧美在线视频免费观看| 91久久精品美女| 欧美在线日韩在线| 亚洲欧美日韩国产精品| 97久久精品人人澡人人爽缅北| 欧美日韩视频免费播放| 亚洲精品美女网站| 欧美性生交xxxxx久久久| 欧美日韩福利视频| 欧美中文在线字幕| 成人免费xxxxx在线观看| 国产日本欧美一区| 亚洲成人精品久久| 日韩精品在线私人| 亚洲精品色婷婷福利天堂| 91午夜在线播放| 91系列在线播放| 国产精品999| 久久国产精品久久久久久久久久| 精品自拍视频在线观看| 欧美亚洲国产另类| 久久99亚洲精品| 欧美专区福利在线| 92国产精品视频| 日韩在线观看网址| 欧美极品少妇xxxxⅹ喷水| 九九九热精品免费视频观看网站| 亚洲成人精品视频| 亚洲视频在线免费观看| 国产精品自拍偷拍| 日韩中文在线观看| 久久精品一本久久99精品| 日韩免费观看高清| 欧美一区第一页| 91精品国产乱码久久久久久久久| 91麻豆国产语对白在线观看| 国产999视频| 日韩激情av在线播放| 国产精品中文字幕久久久| 国产精品久久999| 欧美日韩国产在线看| 人人爽久久涩噜噜噜网站| 欧美日韩xxxxx|