JS深拷貝和淺拷貝的實(shí)現(xiàn)

一鬼吵、JS的基本數(shù)據(jù)類(lèi)型

  • 基本數(shù)據(jù)類(lèi)型:String,Boolean篮赢,Number齿椅,Undefined,Null启泣;
  • 引用數(shù)據(jù)類(lèi)型:Object(Array涣脚,Date,RegExp寥茫,F(xiàn)unction)遣蚀;
  • 基本數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型的區(qū)別:
    1、保存位置不同:基本數(shù)據(jù)類(lèi)型保存在棧內(nèi)存中纱耻,引用數(shù)據(jù)類(lèi)型保存在堆內(nèi)存中芭梯,然后在棧內(nèi)存中保存了一個(gè)對(duì)堆內(nèi)存中實(shí)際對(duì)象的引用,即數(shù)據(jù)在堆內(nèi)存中的地址弄喘,JS對(duì)引用數(shù)據(jù)類(lèi)型的操作都是操作對(duì)象的引用而不是實(shí)際的對(duì)象玖喘,如果obj1拷貝了obj2,那么這兩個(gè)引用數(shù)據(jù)類(lèi)型就指向了同一個(gè)堆內(nèi)存對(duì)象限次,具體操作是obj1將棧內(nèi)存的引用地址復(fù)制了一份給obj2芒涡,因而它們共同指向了一個(gè)堆內(nèi)存對(duì)象;
    為什么基本數(shù)據(jù)類(lèi)型保存在棧中卖漫,而引用數(shù)據(jù)類(lèi)型保存在堆中费尽?
    1)堆比棧大,棧比堆速度快羊始;
    2)基本數(shù)據(jù)類(lèi)型比較穩(wěn)定旱幼,而且相對(duì)來(lái)說(shuō)占用的內(nèi)存小突委;
    3)引用數(shù)據(jù)類(lèi)型大小是動(dòng)態(tài)的柏卤,而且是無(wú)限的,引用值的大小會(huì)改變匀油,不能把它放在棧中缘缚,否則會(huì)降低變量查找的速度,因此放在變量椀醒粒空間的值是該對(duì)象存儲(chǔ)在堆中的地址桥滨,地址的大小是固定的,所以把它存儲(chǔ)在棧中對(duì)變量性能無(wú)任何負(fù)面影響;
    4)堆內(nèi)存是無(wú)序存儲(chǔ)齐媒,可以根據(jù)引用直接獲绕衙俊;

按引用訪問(wèn):js不允許直接訪問(wèn)保存在堆內(nèi)存中的對(duì)象喻括,所以在訪問(wèn)一個(gè)對(duì)象時(shí)邀杏,首先得到的是這個(gè)對(duì)象在堆內(nèi)存中的地址,然后再按照這個(gè)地址去獲得這個(gè)對(duì)象中的值唬血;

ECMAScript中所有函數(shù)的參數(shù)都是按值來(lái)傳遞的望蜡,對(duì)于原始值,只是把變量里的值傳遞給參數(shù)拷恨,之后參數(shù)和這個(gè)變量互不影響泣特,對(duì)于引用值,對(duì)象變量里面的值是這個(gè)對(duì)象在堆內(nèi)存中的內(nèi)存地址挑随,因此它傳遞的值也就是這個(gè)內(nèi)存地址,這也就是為什么函數(shù)內(nèi)部對(duì)這個(gè)參數(shù)的修改會(huì)體現(xiàn)在外部的原因勒叠,因?yàn)樗鼈兌贾赶蛲粋€(gè)對(duì)象兜挨;
2、基本數(shù)據(jù)類(lèi)型使用typeof可以返回其基本數(shù)據(jù)類(lèi)型眯分,但是NULL類(lèi)型會(huì)返回object拌汇,因此null值表示一個(gè)空對(duì)象指針;引用數(shù)據(jù)類(lèi)型使用typeof會(huì)返回object弊决,此時(shí)需要使用instanceof來(lái)檢測(cè)引用數(shù)據(jù)類(lèi)型噪舀;
3、定義引用數(shù)據(jù)類(lèi)型需要使用new操作符飘诗,后面再跟一個(gè)構(gòu)造函數(shù)來(lái)創(chuàng)建与倡;
1)使用new操作符創(chuàng)建對(duì)象;

var obj1 = new Object();
obj1.a = 1;

2)使用對(duì)象字面量表示法創(chuàng)建對(duì)象昆稿;

var obj1 = {
  a: 1,
  b: 2
}

3)可以通過(guò)點(diǎn)表示法訪問(wèn)對(duì)象的屬性纺座,也可以使用方括號(hào)表示法來(lái)訪問(wèn)對(duì)象的屬性;

  • ES6新增數(shù)據(jù)類(lèi)型:Map溉潭,Set净响,Generator,Symbol
  • 本地對(duì)象:ECMA-262 把本地對(duì)象(native object)定義為“獨(dú)立于宿主環(huán)境的 ECMAScript 實(shí)現(xiàn)提供的對(duì)象”喳瓣,即本地對(duì)象就是 ECMA-262 定義的類(lèi)(引用類(lèi)型)馋贤;
  • 宿主對(duì)象:宿主”就是我們網(wǎng)頁(yè)的運(yùn)行環(huán)境,即“操作系統(tǒng)”和“瀏覽器”畏陕,所有非本地對(duì)象都是宿主對(duì)象(host object)配乓,即由 ECMAScript 實(shí)現(xiàn)的宿主環(huán)境提供的對(duì)象,所有的BOM和DOM對(duì)象都是宿主對(duì)象,因?yàn)槠鋵?duì)于不同的“宿主”環(huán)境所展示的內(nèi)容不同扰付,即ECMAScript官方未定義的對(duì)象都屬于宿主對(duì)象堤撵,因?yàn)槠湮炊x的對(duì)象大多數(shù)是自己通過(guò)ECMAScript程序創(chuàng)建的對(duì)象;
  • JS內(nèi)置對(duì)象:是指JS語(yǔ)言自帶的一些對(duì)象羽莺,供開(kāi)發(fā)者使用实昨,這些對(duì)象提供了一些常用的或是最基本而必要的功能;
    1盐固、Arguments:函數(shù)參數(shù)集合荒给;
    2、Array對(duì)象:length,instanceof,isArray(),toString()返回字符串,valueOf()返回?cái)?shù)組的值,join()可以將數(shù)組轉(zhuǎn)為字符串,push(),pop(),shift(),unshift(),reverse(),sort(),
    slice(),splice(),indexOf(),lastIndexOf(),迭every(),filter(),forEach(),map(),some(),
    歸并方法reduce(),reduceRight()刁卜;
    3志电、Boolean:布爾對(duì)象;
    4蛔趴、Error:異常對(duì)象挑辆;
    5、Number:數(shù)值對(duì)象孝情;
    6鱼蝉、String對(duì)象:length,charAt()返回指定位置的字符,concat(),slice(),subString(),
    subStr(),indexOf(),lastIndexOf(),trim(),toLowerCase(),toUpperCase(),split(),
    text.match(),text.splice();
    7箫荡、Date對(duì)象:toUTCstring(),getTime()魁亦;
    8、RegExp對(duì)象:test()羔挡;
    9洁奈、Function對(duì)象:arguments,this,apply(this,arguments),call(this,num1,num2);
    10绞灼、Math對(duì)象:min(),max(),ceil(),floor(),round(),random()利术;
    11、Global對(duì)象:encodeURI,encodeURIComponent低矮,parseInt(),eval()氯哮;
    12、Object對(duì)象:prototype,constructor商佛;
  • 基本包裝類(lèi)型:Boolean,Number,String
    1喉钢、轉(zhuǎn)換為數(shù)值:parseInt()專(zhuān)門(mén)用于把字符串轉(zhuǎn)換成數(shù)值,Number()用于任何類(lèi)型;
    2良姆、非字符轉(zhuǎn)換成字符:toString()肠虽;
    3、數(shù)組轉(zhuǎn)成字符:join()玛追;
    4税课、字符串轉(zhuǎn)換成數(shù)組:split()闲延;
// 基本數(shù)據(jù)類(lèi)型的復(fù)制,基本數(shù)據(jù)類(lèi)型是按值傳遞的
var a = 1;
var b = a;
b = 2;
console.log(a);  // 1
console.log(b);  // 2
// 引用數(shù)據(jù)類(lèi)型的復(fù)制韩玩,引用數(shù)據(jù)類(lèi)型按引用傳值
var obj1 = {
  a: 1,
  b: 2
}
var obj2 = obj1;
obj2.a = 3;
console.log(obj1.a); // 3
console.log(obj2.a); // 3

二垒玲、JS淺拷貝

深拷貝和淺拷貝

  • 深拷貝和淺拷貝簡(jiǎn)單解釋
    ?????淺拷貝和深拷貝都只針對(duì)于引用數(shù)據(jù)類(lèi)型,淺拷貝只復(fù)制指向某個(gè)對(duì)象的指針找颓,而不復(fù)制對(duì)象本身合愈,新舊對(duì)象還是共享同一塊內(nèi)存;但深拷貝會(huì)另外創(chuàng)造一個(gè)一模一樣的對(duì)象击狮,新對(duì)象跟原對(duì)象不共享內(nèi)存佛析,修改新對(duì)象不會(huì)改到原對(duì)象;
    ?????區(qū)別:淺拷貝只復(fù)制對(duì)象的第一層屬性彪蓬、深拷貝可以對(duì)對(duì)象的屬性進(jìn)行遞歸復(fù)制寸莫;
// 只復(fù)制第一層的淺拷貝
function simpleCopy(obj1) {
   var obj2 = Array.isArray(obj1) ? [] : {};
   for (let i in obj1) {
   obj2[i] = obj1[i];
  }
   return obj2;
}
var obj1 = {
   a: 1,
   b: 2,
   c: {
   d: 3
  }
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4
  • Object.assign()實(shí)現(xiàn)淺拷貝及一層的深拷貝
let obj1 = {
   a: {
     b: 1
   },
   c: 2
}
let obj2 = Object.assign({},obj1)
obj2.a.b = 3;
obj2.c = 3
console.log(obj1.a.b); // 3
console.log(obj2.a.b); // 3
console.log(obj1.c); // 2
console.log(obj2.c); // 3

二、JS深拷貝

  • 手動(dòng)實(shí)現(xiàn)深拷貝
let obj1 = {
   a: 1,
   b: 2
}
let obj2 = {
   a: obj1.a,
   b: obj1.b
}
obj2.a = 3;
alert(obj1.a); // 1
alert(obj2.a); // 3
let obj1 = {
   a: {
     b: 2
   }
}
let obj2 = {
   a: obj1.a
}
obj2.a.b = 3;
console.log(obj1.a.b); // 3
console.log(obj2.a.b); // 3
  • 遞歸實(shí)現(xiàn)深拷貝
function deepCopy(obj1) {
      var obj2 = Array.isArray(obj1) ? [] : {};
      if (obj1 && typeof obj1 === "object") {
        for (var i in obj1) {
          if (obj1.hasOwnProperty(i)) {
            // 如果子屬性為引用數(shù)據(jù)類(lèi)型档冬,遞歸復(fù)制
            if (obj1[i] && typeof obj1[i] === "object") {
              obj2[i] = deepCopy(obj1[i]);
            } else {
              // 如果是基本數(shù)據(jù)類(lèi)型膘茎,只是簡(jiǎn)單的復(fù)制
              obj2[i] = obj1[i];
            }
          }
        }
      }
      return obj2;
    }
    var obj1 = {
      a: 1,
      b: 2,
      c: {
        d: 3
      }
    }
    var obj2 = deepCopy(obj1);
    obj2.a = 3;
    obj2.c.d = 4;
    alert(obj1.a); // 1
    alert(obj2.a); // 3
    alert(obj1.c.d); // 3
    alert(obj2.c.d); // 4

缺陷:當(dāng)遇到兩個(gè)互相引用的對(duì)象,會(huì)出現(xiàn)死循環(huán)的情況酷誓,為了避免相互引用的對(duì)象導(dǎo)致死循環(huán)的情況辽狈,則應(yīng)該在遍歷的時(shí)候判斷是否相互引用對(duì)象,如果是則退出循環(huán)呛牲;

function deepCopy(obj1) {
      var obj2 = Array.isArray(obj1) ? [] : {};
      if (obj1 && typeof obj1 === "object") {
        for (var i in obj1) {
          var prop = obj1[i]; // 避免相互引用造成死循環(huán),如obj1.a=obj
          if (prop == obj1) {
            continue;
          }
          if (obj1.hasOwnProperty(i)) {
            // 如果子屬性為引用數(shù)據(jù)類(lèi)型驮配,遞歸復(fù)制
            if (prop && typeof prop === "object") {
              obj2[i] = (prop.constructor === Array) ? [] : {};
              arguments.callee(prop, obj2[i]); // 遞歸調(diào)用
            } else {
              // 如果是基本數(shù)據(jù)類(lèi)型娘扩,只是簡(jiǎn)單的復(fù)制
              obj2[i] = prop;
            }
          }
        }
      }
      return obj2;
    }
    var obj1 = {
      a: 1,
      b: 2,
      c: {
        d: 3
      }
    }
    var obj2 = deepCopy(obj1);
    obj2.a = 3;
    obj2.c.d = 4;
    alert(obj1.a); // 1
    alert(obj2.a); // 3
    alert(obj1.c.d); // 3
    alert(obj2.c.d); // 4
// Object.create實(shí)現(xiàn)深拷貝1,但也只能拷貝一層
function deepCopy(obj1) {
      var obj2 = Array.isArray(obj1) ? [] : {};
      if (obj1 && typeof obj1 === "object") {
        for (var i in obj1) {
          var prop = obj1[i]; // 避免相互引用造成死循環(huán)壮锻,如obj1.a=obj
          if (prop == obj1) {
            continue;
          }
          if (obj1.hasOwnProperty(i)) {
            // 如果子屬性為引用數(shù)據(jù)類(lèi)型琐旁,遞歸復(fù)制
            if (prop && typeof prop === "object") {
              obj2[i] = (prop.constructor === Array) ? [] : Object.create(prop);
            } else {
              // 如果是基本數(shù)據(jù)類(lèi)型,只是簡(jiǎn)單的復(fù)制
              obj2[i] = prop;
            }
          }
        }
      }
      return obj2;
    }
    var obj1 = {
      a: 1,
      b: 2,
      c: {
        d: 3
      }
    }
    var obj2 = deepCopy(obj1);
    obj2.a = 3;
    obj2.c.d = 4;
    alert(obj1.a); // 1
    alert(obj2.a); // 3
    alert(obj1.c.d); // 3
    alert(obj2.c.d); // 4
// Object實(shí)現(xiàn)拷貝2猜绣,淺拷貝
var obj1 = {
      a: 1,
      b: 2,
      c: {
        d: 3
      }
    }
    var obj2 = Object.create(obj1);
    obj2.a = 3;
    obj2.c.d = 4;
    alert(obj1.a); // 1
    alert(obj2.a); // 3
    alert(obj1.c.d); // 4
    alert(obj2.c.d); // 4
  • 使用JSON.stringify和JSON.parse實(shí)現(xiàn)深拷貝:JSON.stringify把對(duì)象轉(zhuǎn)成字符串灰殴,再用JSON.parse把字符串轉(zhuǎn)成新的對(duì)象;
function deepCopy(obj1){
    let _obj = JSON.stringify(obj1);
    let obj2 = JSON.parse(_obj);
    return obj2;
  }
    var a = [1, [1, 2], 3, 4];
    var b = deepCopy(a);
    b[1][0] = 2;
    alert(a); // 1,1,2,3,4
    alert(b); // 2,2,2,3,4

缺陷:它會(huì)拋棄對(duì)象的constructor掰邢,深拷貝之后牺陶,不管這個(gè)對(duì)象原來(lái)的構(gòu)造函數(shù)是什么,在深拷貝之后都會(huì)變成Object辣之;這種方法能正確處理的對(duì)象只有 Number, String, Boolean, Array, 扁平對(duì)象掰伸,也就是說(shuō),只有可以轉(zhuǎn)成JSON格式的對(duì)象才可以這樣用怀估,像function沒(méi)辦法轉(zhuǎn)成JSON狮鸭;

let obj1 = {
   fun:function(){
      alert(123);
   }
}
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun); // function
console.log(typeof obj2.fun); // undefined
  • 熱門(mén)的函數(shù)庫(kù)lodash合搅,也有提供_.cloneDeep用來(lái)做深拷貝;
var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false
  • jquery實(shí)現(xiàn)深拷貝
    jquery 提供一個(gè)$.extend可以用來(lái)做深拷貝歧蕉;
var $ = require('jquery');
var obj1 = {
   a: 1,
   b: {
     f: {
       g: 1
     }
   },
   c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);  // false
  • slice是否為深拷貝
// 對(duì)只有一級(jí)屬性值的數(shù)組對(duì)象使用slice
var a = [1,2,3,4];
var b = a.slice();
b[0] = 2;
alert(a); // 1,2,3,4
alert(b); // 2,2,3,4
// 對(duì)有多層屬性的數(shù)組對(duì)象使用slice
var a = [1,[1,2],3,4];
var b = a.slice();
b[1][0] = 2;
alert(a); // 1,2,2,3,4
alert(b); // 1,2,2,3,4

結(jié)論:slice()和concat()都并非深拷貝灾部;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惯退,隨后出現(xiàn)的幾起案子赌髓,更是在濱河造成了極大的恐慌,老刑警劉巖蒸痹,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件春弥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡叠荠,警方通過(guò)查閱死者的電腦和手機(jī)匿沛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)榛鼎,“玉大人逃呼,你說(shuō)我怎么就攤上這事≌哂椋” “怎么了抡笼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)黄鳍。 經(jīng)常有香客問(wèn)我推姻,道長(zhǎng),這世上最難降的妖魔是什么框沟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任藏古,我火速辦了婚禮,結(jié)果婚禮上忍燥,老公的妹妹穿的比我還像新娘拧晕。我一直安慰自己,他們只是感情好梅垄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布厂捞。 她就那樣靜靜地躺著,像睡著了一般队丝。 火紅的嫁衣襯著肌膚如雪靡馁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天机久,我揣著相機(jī)與錄音奈嘿,去河邊找鬼。 笑死吞加,一個(gè)胖子當(dāng)著我的面吹牛裙犹,可吹牛的內(nèi)容都是我干的尽狠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼叶圃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼袄膏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起掺冠,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沉馆,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后德崭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體斥黑,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年眉厨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锌奴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡憾股,死狀恐怖鹿蜀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情服球,我是刑警寧澤茴恰,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站斩熊,受9級(jí)特大地震影響往枣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粉渠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一分冈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渣叛,春花似錦、人聲如沸盯捌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饺著。三九已至箫攀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間幼衰,已是汗流浹背靴跛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渡嚣,地道東北人梢睛。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓肥印,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親绝葡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子深碱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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