JavaScript深拷貝和淺拷貝

一透绩、如何區(qū)分深拷貝和淺拷貝?

假設B復制了A壁熄,當修改A時帚豪,看B是否發(fā)生變化,如果B也跟著變了草丧,說明這是淺拷貝狸臣,拿人手短;
如果B沒變方仿,那就是深拷貝固棚,自食其力。

二仙蚜、淺拷貝

let a = [0,1,2,3,4];
b = a;
console.log(a===b); // true
a[0] = 1;
console.log(a, b); // [1,1,2,3,4] [1,1,2,3,4]

可以發(fā)現(xiàn),b復制了a厂汗,修改數(shù)組a委粉,數(shù)組b也跟著改變了。
這里娶桦,就要引入js的數(shù)據(jù)類型了贾节。JS數(shù)據(jù)類型包括基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,基本數(shù)據(jù)類型包括string衷畦、number栗涂、Boolean、null祈争、undefined斤程;引用數(shù)據(jù)類型包括Object、arr菩混、函數(shù)等忿墅。

a. 對于基本數(shù)據(jù)類型扁藕,其存儲方式是按值存儲的。如下:
let a = 1 名值存儲在棧內(nèi)存中疚脐。


image.png

當b=a復制時亿柑,棧內(nèi)存會開辟一個新的內(nèi)存,如下:

image.png

所以當你此時修改a=2棍弄,對b并不會造成影響望薄,因為此時的b已經(jīng)自食其力了,不受a影響了呼畸。但是式矫,雖然b不受a影響了,這也算不上深拷貝役耕,因為深拷貝本身是針對較為復雜的object類型數(shù)據(jù)采转。

b. 對于引用數(shù)據(jù)類型,其名存在棧內(nèi)存中瞬痘,值存在堆內(nèi)存中故慈,但是棧內(nèi)存會提供一個引用的地址指向堆內(nèi)存中的值,如下:


image.png

當b=a進行復制時框全,其實復制的是a的引用地址察绷,而并非堆里面的值。a和b指向的是同一個地址津辩。


image.png

當a[0] = 1 對數(shù)組a進行修改時拆撼,由于a和b指向同一個地址,所以b自然也受到影響喘沿,這就是所謂的淺拷貝了闸度。
image.png

要是在堆內(nèi)存中也開辟一個新的內(nèi)存專門為b存放值,這樣就達到深拷貝的效果了蚜印。


image.png

淺拷貝在我看來是對“一層”簡單類型的拷貝莺禁,也就是淺拷貝只對一層有效,是指向不同內(nèi)存地址窄赋。

三哟冬、淺拷貝實現(xiàn)方法

1、直接遍歷

var array = [1,2,3,4];
function copy (array) {
  let newArray = [];
  for (let item of array) {
    newArray.push(item);
  }
  return newArray;
}
var copyArray = copy(array);
copyArray[0] = 100;
console.log(array);  // [1,2,3,4]
console.log(copyArray); // [100,2,3,4]

或者
function shallowCopy (obj) {
  var copyObj = {};
  for (let i in obj) {
    copyObj[i] = obj[i]
  }
  return copyObj
}
var x = {
  a: 1,
  b: {f: {g: 1}},
  c: [1,2,3]
};
var y = shallowCopy(x);
console.log(y); // 與x一樣
console.log(x); // 
y.a = 2;
console.log(y); // y.a = 2
console.log(x); // x.a = 1 不受影響忆绰,因為是a基本類型
y.b.f.g = 3;
console.log(y); // y.b.f.g = 3
console.log(x); // x.b.f.g = 3 受到影響浩峡,因為b是引用類型
y.c.push(4);
console.log(y); // y.c = [1,2,3,4]
console.log(x); // x.c = [1,2,3,4] 受到影響,因為c是引用類型

2错敢、slice()

var array = [1,2,3,4];
var copyArray = array.slice();
copyArray[0] = 100;
console.log(array); // [1,2,3,4]
console.log(copyArray); // [100,2,3,4]

slice() 方法返回一個從已有的數(shù)組中截取一部分元素片段組成的新數(shù)組(不改變原來的數(shù)組)翰灾,當slice不帶任何參數(shù)時,默認返回一個長度和原數(shù)組相同的數(shù)組。

3预侯、concat()

var array = [1,2,3,4];
var copyArray = array.concat();
copyArray[0] = 100;
console.log(array); // [1,2,3,4]
console.log(copyArray); // [100,2,3,4]

concat()方法用于連接兩個或多個數(shù)組(該方法不會改變原有的數(shù)組致开,而僅僅會返回被連接數(shù)組的一個版本)。var copyArray = array.concat();實際上相當于var copyArray = array.concat([]); 就是將返回數(shù)組和一個空數(shù)組合并萎馅。

4双戳、Object.assign()

Object.assign(target, ...sources) // target是目標對象,sources是源對象糜芳,可以是多個飒货,修改返回的是目標對象target。
如果目標對象中的屬性具有相同的屬性值峭竣,則屬性將被源對象中的屬性覆蓋塘辅。

const obj1 = {a: {b: 1}}
const obj2 = Object.assign({}, obj1); // {a: {b: 1}}

obj1.a.b = 2;
console.log(obj2.a.b); // 2

Object.assign方法肯定是不會拷貝原型鏈上的屬性,所以模擬實現(xiàn)時需要用hasOwnProperty(..)判斷處理下皆撩,但是直接使用myObject.hasOwnProperty(..)是有問題的扣墩,因為有的對象可能沒有連接到Object.prototype上(通過Object.create(null)來創(chuàng)建),這種情況下扛吞,使用myObject.hasOwnProperty(..)就會失敗呻惕。這個時候,使用call()來解決滥比。

var myObject = Object.create(null);
myObject.b = 2;
Object.prototype.hasOwnProperty.call(myObject, 'b');

但是亚脆,淺拷貝的意思是對于一級數(shù)組元素是基本類型變量的簡單數(shù)組,上面這三種拷貝方式都能成功盲泛,但對于第一級元素是對象或者數(shù)組等引用類型變量的數(shù)組濒持,上面的三種方法就失效了,那就需要深拷貝了寺滚。

四柑营、深拷貝

1、簡單暴力用JSON對象的parse和stringfy

JSON.parse()方法是取一個JSON字符串將其轉(zhuǎn)換為JavaScript對象玛迄。
JSON.stringify()方法取一個JSON對象由境,并將其轉(zhuǎn)換為JSON字符串。
因為怕影響原對象蓖议,所以深拷貝一份對象出來。

function deepClone (obj) {
  let objClone = JSON.parse(JSON.stringify(obj));
  return objClone;
}
let a = [0,1,[2,3],4],
    b = deepClone(a);
a[0] = 1;
a[2][0] = 1;
console.log(b); // [0,1,[2,3],4]

使用JSON.stringify()以及JSON.parse()它是不可以拷貝 undefined 讥蟆, function勒虾, RegExp 等等類型的。不能解決循環(huán)引用的對象瘸彤。

2修然、封裝一個深拷貝的函數(shù)

function deepClone(obj) {
  let objClone = Array.isArray(obj) ? [] : {}; // 或者
  if (obj && typeof obj === 'object') {
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        // 判斷obj子元素是否也是對象,如果是,遞歸復制
        if (obj[key] && typeof obj[key] === 'object') {
          objClone[key] = deepClone(obj[key]);
        } else {
          // 如果不是愕宋,簡單復制
          objClone[key] = obj[key];
        }
      }
    }
  }
  return objClone;
}
let a = [1,2,3,4],
      b = deepClone(a);
a[0] = 2;
console.log(a); // [2,2,3,4]
console.log(b); // [1,2,3,4]

但是玻靡,上面的代碼有很多問題,如下:
1中贝、沒有對參數(shù)做校驗
函數(shù)需要校驗參數(shù)囤捻,如果不是對象的話直接返回

function deepClone(obj) {
  if (!isObject(obj)) return obj;
  // xxx
}

2、判斷是否是對象邏輯不夠嚴謹
判斷對象的嚴謹方法

Object.prototype.toString.call(obj) === '[object Object]';

3邻寿、沒有考慮數(shù)組的兼容
更推薦lodash的深拷貝函數(shù)[https://lodash.com/docs/4.17.15#cloneDeep]

參考文章https://blog.csdn.net/dingyu002/article/details/117994305
----完整校驗的代碼如下:

function deepClone (obj) {
  // 定義一個變量
  let result;
  // 如果當前需要深拷貝的是一個對象的話
  if (obj && typeof obj === 'object') {
    // 如果是一個數(shù)組的話
    if (Array.isArray(obj)) {
      result = []; // 將result賦值為一個數(shù)組蝎土,并且執(zhí)行遍歷
      for (let i in obj) {
        // 遞歸克隆數(shù)組中的每一項
        result.push(deepClone(obj[i]));
      }
    // 如果當前值是null的話,直接賦值為null
    } else if (obj === null) {
      result = null;
    // 如果當前的值是一個RegExp對象的話绣否,直接賦值
    } else if (obj.constructor === RegExp) {
      result = obj;
    // 否則就是普通對象誊涯,直接for in 循環(huán),遞歸賦值對象的所有值
    } else {
      result = {};
      for (let i in obj) {
        result[i] = deepClone(obj[i]);
      }
    }
  // 如果不是對象的話蒜撮,就是基本數(shù)據(jù)類型暴构,那么直接賦值
  } else {
    result = obj;
  }
  // 返回最終結(jié)果
  return result;
}
使用in和hasOwnProperty方法
  • in 操作符會檢查屬性是否在對象及其原型鏈中。
  • hasownProperty()只會檢查是否在myObject對象中段磨,不會檢查原型鏈取逾。
    這里再次強調(diào)下,深拷貝是拷貝對象各個層級的屬性薇溃。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菌赖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沐序,更是在濱河造成了極大的恐慌琉用,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件策幼,死亡現(xiàn)場離奇詭異邑时,居然都是意外死亡,警方通過查閱死者的電腦和手機特姐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門晶丘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人唐含,你說我怎么就攤上這事浅浮。” “怎么了捷枯?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵滚秩,是天一觀的道長。 經(jīng)常有香客問我淮捆,道長郁油,這世上最難降的妖魔是什么本股? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮桐腌,結(jié)果婚禮上拄显,老公的妹妹穿的比我還像新娘。我一直安慰自己案站,他們只是感情好躬审,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嚼吞,像睡著了一般盒件。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舱禽,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天炒刁,我揣著相機與錄音,去河邊找鬼誊稚。 笑死翔始,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的里伯。 我是一名探鬼主播城瞎,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼疾瓮!你這毒婦竟也來了脖镀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狼电,失蹤者是張志新(化名)和其女友劉穎蜒灰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肩碟,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡强窖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了削祈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翅溺。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖髓抑,靈堂內(nèi)的尸體忽然破棺而出咙崎,到底是詐尸還是另有隱情,我是刑警寧澤吨拍,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布夸溶,位于F島的核電站膜宋,受9級特大地震影響瞧毙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锤躁,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迈倍。 院中可真熱鬧鞋屈,春花似錦、人聲如沸刹碾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迷帜。三九已至物舒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戏锹,已是汗流浹背冠胯。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锦针,地道東北人荠察。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像奈搜,于是被迫代替她去往敵國和親悉盆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容