??在說深拷貝、淺拷貝之前呵哨,首先先說一下js里存在的兩種數據類型:基本數據類型和引用數據類型。
- 基本數據類型有:boolean Null Undefined Number String Symbol
- 引用類型有:Object(包含 Array、Function袱饭、RegExp、Date等等)
區(qū)別:基本數據類型名值都在棧內存中呛占,而引用數據類型的名存在棧內存中虑乖,值存在堆內存中,但是棧內存會提供一個引用地址指向堆內存中的值晾虑。
??因為JS不允許直接訪問堆內存疹味,也不能直接對堆內存進行操作仅叫,所以,引用類型的值是按引用訪問的糙捺。
- 深拷貝 & 淺拷貝
??深拷貝诫咱、淺拷貝是專門針對于引用數據類型的一種概念,因為對于引用數據類型的名洪灯、值存在不同的內存空間當中坎缭,所以當發(fā)生拷貝行為的時候,系統(tǒng)會開辟一塊新的棧內存空間签钩,存放的是被復制的變量在棧內存中的值掏呼,即堆內存中的值的引用地址。
??即新的變量復制得到的是被復制對象的引用地址铅檩,當變量發(fā)生改變值的操作時憎夷,指向該對象的地址的其他變量的值也會隨之變化,此為淺拷貝昧旨。
??當然拾给,我們有時候并不希望這樣的場景發(fā)生,所以這時候就需要用到深拷貝的方法去進行拷貝臼予。
堆鸣戴、棧內存.jpg
- 深拷貝 & 淺拷貝常用方法
- 淺拷貝方法:
( = 號只是引用,并沒有發(fā)生拷貝行為粘拾,在內存中并沒有開辟新的空間U!g止汀)
1. slice() // 操作對象:數組 let a = [1,2,3,"4",{name:"lme"}]; let b = a.slice(0); console.log("a: ",a); // [1,2,3,"4",{name:"lme"}] console.log("b: ",b); // [1,2,3,"4",{name:"lme"}] b[4].name = "dsg"; console.log("修改b,打印a: ",a); // [1,2,3,"4",{name:"dsg"}] 2. concat() // 操作對象:數組 let a = [1,2,3,"4",{name:"lme"}]; let b = a.concat(); console.log("a: ",a); // [1,2,3,"4",{name:"lme"}] console.log("b: ",b); // [1,2,3,"4",{name:"lme"}] b[4].name = "cool"; console.log("修改b,打印a: ",a); // [1,2,3,"4",{name:"cool"}] 3. Array.from() // 操作對象:數組 let a = [1,2,3,"4",{name:"lme"}]; let b = Array.from(a); console.log("a: ",a); // [1,2,3,"4",{name:"lme"}] console.log("b: ",b); // [1,2,3,"4",{name:"lme"}] b[4].name = "cool"; console.log("修改b,打印a: ",a); // [1,2,3,"4",{name:"cool"}] 4. ...操作符 // 操作對象:數組 && 對象 let a = [1,2,3,"4",{name:"lme"}]; let b = [...a]; console.log("a: ",a); // [1,2,3,"4",{name:"lme"}] console.log("b: ",b); // [1,2,3,"4",{name:"lme"}] b[4].name = "cool"; console.log("修改b,打印a: ",a); // [1,2,3,"4",{name:"cool"}] let c = {age:18,sex:1,child:{name:"小明",age:2,sex:1}}; let d = {...c}; console.log("c: ", c); // {age:18,sex:1,child:{name: "小明",age: 2,sex: 1}} console.log("d: ", d); // {age:18,sex:1,child:{name: "小明",age: 2,sex: 1}} d.child.name = "小紅"; console.log("修改d,打印c: ", c); // {age:18,sex:1,child:{name: "小紅",age: 2,sex: 1}} 5. Object.assign() // 操作對象:對象 let c = {age:18,sex:1,child:{name:"小明",age:2,sex:1}}; let d = Object.assign({},c); console.log("c: ", c); // {age:18,sex:1,child:{name: "小明",age: 2,sex: 1}} console.log("d: ", d); // {age:18,sex:1,child:{name: "小明",age: 2,sex: 1}} d.child.name = "小紅"; console.log("修改d,打印c: ", c); // {age:18,sex:1,child:{name: "小紅",age: 2,sex: 1}}
- 深拷貝方法:
1. // 當所拷貝的數組或對象的一維元素或屬性都是 基本數據類型 的時候 // 可以使用以上淺拷貝的方法去達成一次深拷貝的結果入偷。 slice()、concat械哟、Array.from()疏之、...操作符、Object.assign() // 注意:當且僅當 數組或對象的一維元素或者屬性全部都是 基本數據類型 的時候 // 拷貝結果才是深拷貝暇咆,否則為淺拷貝7孀Α!爸业! 2. JSON.parse(JSON.stringify(obj)) // 序列化 -> 反序列化 // 將數組或對象序列化轉成字符串其骄,然后再反序列化轉成對象,由于字符串是String類型扯旷, // 屬于基本類型拯爽,所以會切斷與源對象引用地址的聯系而形成一個新的對象。 // 該方法可以對多維對象進行深拷貝钧忽,但是需要注意的是毯炮,在序列化時逼肯,某些特定的值或者類型將會丟失 如下: 1. Date時間對象 // 如果obj里的某一條屬性的值為時間對象,則序列化之后再反序列化得到的結果桃煎, // 時間將只是字符串的形式存在篮幢,而不是時間對象。 2. RegExp为迈、Error對象 // 如果obj里有RegExp洲拇、Error對象,則序列化的結果將只得到空對象曲尸。 3. 函數、undefined // 如果obj里有函數男翰、undefined另患,則序列化的結果會把函數或undefine丟失。 4. NaN // 如果obj里有NaN蛾绎,則序列化的結果會變成null 5. constructor // 如果obj中的某一條屬性的值是由構造函數生成的對象昆箕,則使用 // JSON.parse(JSON.stringify(obj))深拷貝后,會丟棄對象的constructor構造函數租冠。 // 比如: function Person(name) { this.name = name; } let lme = new Person('lme'); let a = { name: 'a', data: lme, }; let b = JSON.parse(JSON.stringify(a)); console.log("a",a); // 構造函數:Person類型 console.log("b",b); // 構造函數:無 b.name = "gyj"; b.data.name = "gyj is lme's wife" console.log("a1: ",a) // 原始a對象沒有被修改 console.log("b1: ",b) // b對象被修改 // 感興趣的同學可以打印一下看看呦~ PS: 該方法簡單粗暴鹏倘,適用于簡單的引用類型數據,使用前請三思~ 3. 自行編寫函數進行遞歸遍歷復制顽爹,實現深拷貝纤泵。 // 先根據要拷貝的變量的類型聲明一個新的變量,用for in遍歷要拷貝的變量镜粤,然后判斷如果當前 // 循環(huán)中的key(屬性)的值是一個對象捏题,那么便遞歸,對該屬性繼續(xù)進行遍歷拷貝肉渴,否則直接將 // 該屬性的值賦值給新的對象的屬性公荧。 // 如果需要將原型鏈上繼承過來的屬性過濾掉的話,可使用hasOwnProperty()同规,該方法會判斷傳 // 入的參數是否在對象上循狰,如果是原型鏈上的屬性或方法,則會返回false券勺。 例: function deepCopy(obj) { let newObj = obj.constructor === Array ? [] : {} for (let key in obj) { // Array實際上也是一個對象绪钥,即也是引用數據類型 typeof obj[key] === 'object' ? newObj[key] = deepCopy(obj[key]) : newObj[key] = obj[key] } return newObj }
- 淺拷貝方法:
(限于本人技術有限,本文如有表述不當的地方朱灿,歡迎賜教~)
(轉載到其他平臺請包含本文的簡書鏈接或說明出處~)