JavaScript中深拷貝與淺拷貝詳解
1 淺拷貝概念
深拷貝和淺拷貝是只針對(duì)Object和Array這樣的引用數(shù)據(jù)類型的。
淺拷貝是創(chuàng)建一個(gè)新對(duì)象,該對(duì)象有著原始對(duì)象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內(nèi)存地址 ,所以如果其中一個(gè)對(duì)象改變了這個(gè)地址,就會(huì)影響到另一個(gè)對(duì)象。
示例代碼:
let people = { //定義一個(gè)People對(duì)象 name: "張三", age: 3, address: "中國(guó)" } console.log("原對(duì)象:", people); let newPeople = people; //進(jìn)行淺拷貝 console.log("新對(duì)象:", newPeople); //原對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } //新對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } //為對(duì)象修改名字 newPeople.name = "橘貓吃不胖"; console.log("原對(duì)象:", people); console.log("新對(duì)象:", newPeople); //原對(duì)象: { name: '橘貓吃不胖', age: 3, address: '中國(guó)' } //新對(duì)象: { name: '橘貓吃不胖', age: 3, address: '中國(guó)' }
從上面的示例可以看出,當(dāng)newPeople的name屬性修改后,原來(lái)的people也發(fā)生了變化,這是因?yàn)?strong>新創(chuàng)建的對(duì)象與舊對(duì)象具有相同的內(nèi)存地址。
2 深拷貝概念
深拷貝是將一個(gè)對(duì)象從內(nèi)存中完整的拷貝一份出來(lái),從堆內(nèi)存中開(kāi)辟一個(gè)新的區(qū)域存放新對(duì)象,且修改新對(duì)象不會(huì)影響原對(duì)象。
示例代碼:
let people = { //定義一個(gè)People對(duì)象 name: "張三", age: 3, address: "中國(guó)" } //對(duì)people進(jìn)行深拷貝 let newPeople = JSON.parse(JSON.stringify(people)); console.log("原對(duì)象:", people); console.log("新對(duì)象:", newPeople); // 原對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } // 新對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } //修改新對(duì)象中的adress屬性 newPeople.address = "俄羅斯"; console.log("原對(duì)象:", people); console.log("新對(duì)象:", newPeople); // 原對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } // 新對(duì)象: { name: '張三', age: 3, address: '俄羅斯' }
從上面的例子可以看出,深拷貝后,修改新對(duì)象,不會(huì)影響原對(duì)象。
3 淺拷貝的實(shí)現(xiàn)方式
3.1 Object.assign()
Object.assign()
方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象分配到目標(biāo)對(duì)象。它將返回目標(biāo)對(duì)象。Object.assign() 進(jìn)行的是淺拷貝,拷貝的是對(duì)象的屬性的引用,而不是對(duì)象本身。
語(yǔ)法:
Object.assign(target, ...sources) //target:目標(biāo)對(duì)象;sources:源對(duì)象。
如果目標(biāo)對(duì)象中的屬性具有相同的鍵,則屬性將被源對(duì)象中的屬性覆蓋。后面的源對(duì)象的屬性將類似地覆蓋前面的源對(duì)象的屬性。
示例:
const target = { a: 1, b: 2 }; const source = { b: 4, c: 5 }; //進(jìn)行淺拷貝 const returnedTarget = Object.assign(target, source); console.log(target); console.log(returnedTarget); // { a: 1, b: 4, c: 5 } // { a: 1, b: 4, c: 5 } //修改其中的值 target.b = 10; console.log(target); console.log(returnedTarget); // { a: 1, b: 10, c: 5 } // { a: 1, b: 10, c: 5 }
當(dāng)對(duì)象object只有一層的時(shí)候,是深拷貝,示例代碼如下:
const obj = { name: "橘貓吃不胖" }; //進(jìn)行淺拷貝 let newObj = Object.assign({}, obj); //修改新對(duì)象中的name屬性為張三 newObj.name = "張三"; console.log("原對(duì)象:", obj); console.log("新對(duì)象:", newObj); // 原對(duì)象: { name: '橘貓吃不胖' } // 新對(duì)象: { name: '張三' }
3.2 Array.prototype.concat()
concat()方法用于合并兩個(gè)或多個(gè)數(shù)組。此方法不會(huì)更改現(xiàn)有數(shù)組,而是返回一個(gè)新數(shù)組。
語(yǔ)法:
var new_array = old_array.concat(value1[, value2[, ...[, valueN]]]) //valueN可選,數(shù)組和/或值,將被合并到一個(gè)新的數(shù)組中。 //如果省略了所有 valueN 參數(shù),則 concat 會(huì)返回調(diào)用此方法的現(xiàn)存數(shù)組的一個(gè)淺拷貝。
示例代碼:
let arr1 = [1, 2, { name: "橘貓吃不胖" }]; //進(jìn)行淺拷貝 let arr2 = arr1.concat(); console.log("原數(shù)組:", arr1); console.log("新數(shù)組:", arr2); // 原數(shù)組: [ 1, 2, { name: '橘貓吃不胖' } ] // 新數(shù)組: [ 1, 2, { name: '橘貓吃不胖' } ] //修改原數(shù)組 arr1[1] = "hhhhh"; console.log("原數(shù)組:", arr1); console.log("新數(shù)組:", arr2); // 原數(shù)組: [ 1, 'hhhhh', { name: '橘貓吃不胖' } ] // 新數(shù)組: [ 1, 2, { name: '橘貓吃不胖' } ]
3.3 Array.prototype.slice()
slice() 方法返回一個(gè)新的數(shù)組對(duì)象,這一對(duì)象是一個(gè)由 begin 和 end 決定的原數(shù)組的淺拷貝(包括 begin,不包括end)。原始數(shù)組不會(huì)被改變。
語(yǔ)法:
arr.slice([begin[, end]]) //begin:可選,提取起始處的索引(從 0 開(kāi)始),從該索引開(kāi)始提取原數(shù)組元素。 //如果該參數(shù)為負(fù)數(shù),則表示從原數(shù)組中的倒數(shù)第幾個(gè)元素開(kāi)始提取,slice(-2) 表示提取原數(shù)組中的倒數(shù)第二個(gè)元素到最后一個(gè)元素(包含最后一個(gè)元素)。 //如果省略 begin,則 slice 從索引 0 開(kāi)始。 //如果 begin 超出原數(shù)組的索引范圍,則會(huì)返回空數(shù)組。 //end:可選,提取終止處的索引(從 0 開(kāi)始),在該索引處結(jié)束提取原數(shù)組元素。slice 會(huì)提取原數(shù)組中索引從 begin 到 end 的所有元素(包含 begin,但不包含 end)。 //slice(1,4) 會(huì)提取原數(shù)組中從第二個(gè)元素開(kāi)始一直到第四個(gè)元素的所有元素 (索引為 1, 2, 3的元素)。 //如果該參數(shù)為負(fù)數(shù), 則它表示在原數(shù)組中的倒數(shù)第幾個(gè)元素結(jié)束抽取。 slice(-2,-1) 表示抽取了原數(shù)組中的倒數(shù)第二個(gè)元素到最后一個(gè)元素(不包含最后一個(gè)元素,也就是只有倒數(shù)第二個(gè)元素)。 //如果 end 被省略,則 slice 會(huì)一直提取到原數(shù)組末尾。 //如果 end 大于數(shù)組的長(zhǎng)度,slice 也會(huì)一直提取到原數(shù)組末尾。
示例代碼:
let arr1 = [1, 2, { name: "橘貓吃不胖" }]; //進(jìn)行淺拷貝 let arr2 = arr1.slice(); console.log("原數(shù)組:", arr1); console.log("新數(shù)組:", arr2); // 原數(shù)組: [ 1, 2, { name: '橘貓吃不胖' } ] // 新數(shù)組: [ 1, 2, { name: '橘貓吃不胖' } ] //修改原數(shù)組 arr1[1] = "hhhhh"; console.log("原數(shù)組:", arr1); console.log("新數(shù)組:", arr2); // 原數(shù)組: [ 1, 'hhhhh', { name: '橘貓吃不胖' } ] // 新數(shù)組: [ 1, 2, { name: '橘貓吃不胖' } ]
3.4 直接賦值
直接使用“=”賦值可以實(shí)現(xiàn)淺拷貝,示例代碼如下:
let obj1 = { //定義一個(gè)對(duì)象obj1 name: "張三", age: 34 } let obj2 = obj1; //進(jìn)行淺拷貝 console.log("obj1:", obj1); console.log("obj2:", obj2); // obj1: { name: '張三', age: 34 } // obj2: { name: '張三', age: 34 } //修改obj2中的name屬性 obj2.name = "橘貓吃不胖"; console.log("obj1:", obj1); console.log("obj2:", obj2); // obj1: { name: '橘貓吃不胖', age: 34 } // obj2: { name: '橘貓吃不胖', age: 34 }
4 深拷貝的實(shí)現(xiàn)方式
4.1 JSON.parse(JSON.stringify())
JSON是一種語(yǔ)法,用來(lái)序列化對(duì)象、數(shù)組、數(shù)值、字符串、布爾值和 null 。它基于JavaScript語(yǔ)法,但與之不同:JavaScript不是JSON,JSON也不是JavaScript。
JSON對(duì)象包含兩個(gè)方法:用于解析JSON的parse()方法,以及將對(duì)象/值轉(zhuǎn)換為JSON字符串的stringify()方法,下面對(duì)這兩種方法進(jìn)行一些介紹。
JSON.parse()
方法用來(lái)解析JSON字符串,構(gòu)造由字符串描述的JavaScript值或?qū)ο蟆L峁┛蛇x的 reviver 函數(shù)用以在返回之前對(duì)所得到的對(duì)象執(zhí)行變換(操作)。
語(yǔ)法:
JSON.parse(text[, reviver]) //text:要被解析成 JavaScript 值的字符串 //reviver,可選,轉(zhuǎn)換器, 如果傳入該參數(shù)(函數(shù)),可以用來(lái)修改解析生成的原始值,調(diào)用時(shí)機(jī)在 parse 函數(shù)返回之前。
示例:
JSON.parse('{}'); // {} JSON.parse('true'); // true JSON.parse('"foo"'); // "foo" JSON.parse('[1, 5, "false"]'); // [1, 5, "false"] JSON.parse('null'); // null
JSON.stringify()
方法將一個(gè) JavaScript 對(duì)象或值轉(zhuǎn)換為 JSON 字符串,如果指定了一個(gè) replacer 函數(shù),則可以選擇性地替換值,或者指定的 replacer 是數(shù)組,則可選擇性地僅包含數(shù)組指定的屬性。
語(yǔ)法:
JSON.stringify(value[, replacer [, space]]) //value:將要序列化成 一個(gè) JSON 字符串的值。 //replacer,可選,如果該參數(shù)是一個(gè)函數(shù),則在序列化過(guò)程中,被序列化的值的每個(gè)屬性都會(huì)經(jīng)過(guò)該函數(shù)的轉(zhuǎn)換和處理;如果該參數(shù)是一個(gè)數(shù)組,則只有包含在這個(gè)數(shù)組中的屬性名才會(huì)被序列化到最終的 JSON 字符串中;如果該參數(shù)為 null 或者未提供,則對(duì)象所有的屬性都會(huì)被序列化。 //space,可選,指定縮進(jìn)用的空白字符串,用于美化輸出(pretty-print);如果參數(shù)是個(gè)數(shù)字,它代表有多少的空格;上限為10。該值若小于1,則意味著沒(méi)有空格;如果該參數(shù)為字符串(當(dāng)字符串長(zhǎng)度超過(guò)10個(gè)字母,取其前10個(gè)字母),該字符串將被作為空格;如果該參數(shù)沒(méi)有提供(或者為 null),將沒(méi)有空格。
示例:
JSON.stringify({}); // '{}' JSON.stringify(true); // 'true' JSON.stringify("foo"); // '"foo"' JSON.stringify([1, "false", false]); // '[1,"false",false]' JSON.stringify({ x: 5 }); // '{"x":5}'
深拷貝示例代碼:
let people = { //定義一個(gè)People對(duì)象 name: "張三", age: 3, address: "中國(guó)" } //對(duì)people進(jìn)行深拷貝 let newPeople = JSON.parse(JSON.stringify(people)); console.log("原對(duì)象:", people); console.log("新對(duì)象:", newPeople); // 原對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } // 新對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } //修改新對(duì)象中的adress屬性 newPeople.address = "俄羅斯"; console.log("原對(duì)象:", people); console.log("新對(duì)象:", newPeople); // 原對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } // 新對(duì)象: { name: '張三', age: 3, address: '俄羅斯' }
用JSON.stringify將對(duì)象轉(zhuǎn)成JSON字符串,再用JSON.parse()把字符串解析成對(duì)象,這樣新的對(duì)象產(chǎn)生了,實(shí)現(xiàn)深拷貝。這種方法雖然可以實(shí)現(xiàn)數(shù)組或?qū)ο笊羁截悾荒芴幚砗瘮?shù)。
let arr1 = [1, 2, { name: "橘貓吃不胖" }, function () { }]; //進(jìn)行深拷貝 let arr2 = JSON.parse(JSON.stringify(arr1)); console.log("原數(shù)組:", arr1); console.log("新數(shù)組:", arr2); // 原數(shù)組: [ 1, 2, { name: '橘貓吃不胖' }, [Function (anonymous)] ] // 新數(shù)組: [ 1, 2, { name: '橘貓吃不胖' }, null ]
由上面例子可以看出,函數(shù)并沒(méi)有被拷貝在arr2中。這是因?yàn)?JSON.stringify() 方法是將一個(gè)JavaScript值(對(duì)象或者數(shù)組)轉(zhuǎn)換為一個(gè) JSON字符串,不能接受函數(shù)。
4.2 函數(shù)庫(kù)lodash
Lodash是一個(gè)JavaScript庫(kù),提供了多個(gè)實(shí)用程序功能,而Lodash庫(kù)中最常用的功能之一是cloneDeep()方法。此方法有助于深度克隆對(duì)象,還可以克隆JSON.stringify()方法的局限性,即不可序列化的屬性。
示例代碼:
const lodash = require("lodash"); let people = { name: "張三", age: 3, address: "中國(guó)" } //對(duì)people進(jìn)行深拷貝 let newPeople = lodash.cloneDeep(people); console.log("原對(duì)象:", people); console.log("新對(duì)象:", newPeople); // 原對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } // 新對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } //修改新對(duì)象中的adress屬性 newPeople.address = "俄羅斯"; newPeople.name = "橘貓吃不胖"; console.log("原對(duì)象:", people); console.log("新對(duì)象:", newPeople); // 原對(duì)象: { name: '張三', age: 3, address: '中國(guó)' } // 新對(duì)象: { name: '橘貓吃不胖', age: 3, address: '俄羅斯' }
總結(jié)
到此這篇關(guān)于JavaScript中深拷貝與淺拷貝詳解的文章就介紹到這了,更多相關(guān)JavaScript深拷貝與淺拷貝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript之Blob對(duì)象類型的具體使用方法
這篇文章主要介紹了JavaScript之Blob對(duì)象類型的具體使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11setInterval 和 setTimeout會(huì)產(chǎn)生內(nèi)存溢出
jscript 5.7 發(fā)布修復(fù)了不少ie javascript內(nèi)存泄露的問(wèn)題。但是leak依然存在。當(dāng)我們頻繁使用 setInterval 和 setTimeout 時(shí)就會(huì)每幾秒鐘出現(xiàn)32k leak...2008-02-02Bootstrap 模態(tài)框(Modal)插件代碼解析
Bootstrap 模態(tài)框(Modal)插件 模態(tài)框(Modal)是覆蓋在父窗體上的子窗體。這篇文章主要介紹了Bootstrap 模態(tài)框(Modal)插件代碼解析的相關(guān)資料,需要的朋友可以參考下2016-12-12JS+CSS實(shí)現(xiàn)的經(jīng)典圓角下拉菜單效果代碼
這篇文章主要介紹了JS+CSS實(shí)現(xiàn)的經(jīng)典圓角下拉菜單效果代碼,可實(shí)現(xiàn)非常經(jīng)典的圓角下拉菜單效果,涉及JavaScript動(dòng)態(tài)操作頁(yè)面元素css樣式的相關(guān)技巧,需要的朋友可以參考下2015-10-10JavaScript css3實(shí)現(xiàn)簡(jiǎn)單視頻彈幕功能
這篇文章主要為大家詳細(xì)介紹了JavaScript css3實(shí)現(xiàn)簡(jiǎn)單視頻彈幕功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07基于javascript實(shí)現(xiàn)判斷移動(dòng)終端瀏覽器版本信息
這篇文章主要介紹了基于javascript實(shí)現(xiàn)判斷移動(dòng)終端瀏覽器版本信息,需要的朋友可以參考下2014-12-12詳解JS如何實(shí)現(xiàn)文字溢出時(shí)用省略號(hào)...顯示
這篇文章主要為大家詳細(xì)介紹了JavaScript如何實(shí)現(xiàn)當(dāng)文本內(nèi)容過(guò)長(zhǎng)時(shí),中間顯示省略號(hào)...,兩端正常展示,有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02使用濾鏡設(shè)置透明導(dǎo)致 IE 6/7/8/9 解析異常的解決方法
使用濾鏡設(shè)置透明導(dǎo)致 IE 6/7/8/9 解析異常的解決方法,需要的朋友可以參考下。2011-04-04