摘要:引用數(shù)據(jù)類型是存放在堆內(nèi)存中的,變量實(shí)際上是一個存放在棧內(nèi)存的指針,這個指針指向堆內(nèi)存中的地址。棧和堆的區(qū)別其實(shí)淺拷貝和深拷貝的主要區(qū)別就是數(shù)據(jù)在內(nèi)存中的存儲類型不同。這里,對存在子對象的對象進(jìn)行拷貝的時候,就是深拷貝了。
數(shù)據(jù)類型
在開始拷貝之前,我們從JavaScript的數(shù)據(jù)類型和內(nèi)存存放地址講起。
數(shù)據(jù)類型分為基本數(shù)據(jù)類型 和引用數(shù)據(jù)類型
基本數(shù)據(jù)類型主要包括undefined,boolean,number, string,null。
基本數(shù)據(jù)類型主要存放在棧(stack),存放在棧中的數(shù)據(jù)簡單,大小確定。存放在棧內(nèi)存中的數(shù)據(jù)是直接按值存放的,是可以直接訪問的。
基本數(shù)據(jù)類型的比較是值的比較,只要它們的值相等就認(rèn)為它們是相等的。
let a = 1; let b = 1; console.log(a === b); //true
這里使用嚴(yán)格相等,主要是為了==會進(jìn)行類型轉(zhuǎn)換。
let a = true; let b = 1; console.log(a == b); //true
引用數(shù)據(jù)類型也就是對象類型Object type,比如object,array, function,date等。
引用數(shù)據(jù)類型是存放在堆(heap)內(nèi)存中的,變量實(shí)際上是一個存放在棧內(nèi)存的指針,這個指針指向堆內(nèi)存中的地址。
引用數(shù)據(jù)類型的比較是引用的比較
所以我們每次對js中的引用類型進(jìn)行操作的時候,都是操作其保存在棧內(nèi)存中的指針,所以比較兩個引用數(shù)據(jù)類型,是看它們的指針是否指向同一個對象。
let foo = {a: 1, b: 2}; let bar = {a: 1, b: 2}; console.log(foo === bar); //false
雖然變量foo和變量bar所表示的內(nèi)容是一樣的,但是其在內(nèi)存中的位置不一樣,也就是變量foo和bar在棧內(nèi)存中存放的指針指向的不是堆內(nèi)存中的同一個對象,所以它們是不相等的。
棧和堆的區(qū)別其實(shí)淺拷貝和深拷貝的主要區(qū)別就是數(shù)據(jù)在內(nèi)存中的存儲類型不同。
棧和堆都是內(nèi)存中劃分出來用來存儲的區(qū)域。
棧(stack) 是自動分配的內(nèi)存空間,由系統(tǒng)自動釋放;
堆(heap) 則是動態(tài)分配的內(nèi)存,大小不定也不會自動釋放。
如果你的對象只有值類型的屬性,可以用ES6的新語法Object.assign(...)實(shí)現(xiàn)拷貝
//淺拷貝 let obj = {foo: "foo", bar: "bar"}; let shallowCopy = { ...obj }; //{foo: "foo", bar: "bar"}
//淺拷貝 let obj = {foo: "foo", bar: "bar"}; let shallowCopy = Object.assign({}, obj); //{foo: "foo", bar: "bar"}
我們接著來看下淺拷貝和賦值(=) 的區(qū)別
let obj = {foo: "foo", bar: "bar"}; let shallowCopy = { ...obj }; //{foo: "foo", bar: "bar"} let obj2= obj; //{foo: "foo", bar: "bar"} shallowCopy.foo = 1; obj2.bar = 1 console.log(obj); //{foo: "foo", bar: 1};
可以看出賦值得到的obj2和最初的obj指向的是同一對象,改變數(shù)據(jù)會使原數(shù)據(jù)一同改變。
而淺拷貝得到的shallowCopy則將obj的第一層數(shù)據(jù)對象拷貝到了,和源數(shù)據(jù)不指向同一對象,改變不會使原數(shù)據(jù)一同改變。
但是Object.assign(...)方法只能進(jìn)行對象的一層拷貝。對于對象的屬性是對象的對象,他不能進(jìn)行深層拷貝。
迷糊了吧?直接代碼解釋
let foo = {a: 0, b: {c: 0}}; let copy = { ...foo }; copy.a = 1; copy.b.c = 1; console.log(copy); //{a: 1, b: {c: 1}}; console.log(foo); //{a: 0, b: {c: 1}};
可以看到,使用Object.assign(...)方法拷貝的copy對象的二層對象發(fā)生改變的時候,依然會使原數(shù)據(jù)一同改變。
這里,對存在子對象的對象進(jìn)行拷貝的時候,就是深拷貝了。
淺拷貝:將B對象拷貝到A對象中,不包括B里面的子對象
深拷貝:將B對象拷貝到A對象中,包括B里面的子對象
深拷貝實(shí)現(xiàn)的方法:
這里只說幾種常用方法,
1.JSON.parse(JSON.stringify( ));
let foo = {a: 0, b: {c: 0}}; let copy = JSON.parse(JSON.stringify(foo)); copy.a = 1; copy.b.c = 1; console.log(copy); //{a: 1, b: {c: 1}}; console.log(foo); //{a: 0, b: {c: 0}};
2.遞歸拷貝
function deepCopy(initialObj, finalObj){ let obj = finalObj || {}; for(let i in initialObj) { if(typeof initialObj[i] === "object") { obj[i] = (initialObj[i].constructor === Array) ? [] : {}; arguments.callee(initialObj[i], obj[i]); }else{ obj[i] = initialObj[i]; } } return obj; } var foo = {a: 0, b: {c: 0}}; var str = {}; deepCopy(foo, str); str.a = 1; str.b.c = 1; console.log(str); //{a: 1, b: {c: 1}}; console.log(foo); //{a: 0, b: {c: 0}};
上述代碼確實(shí)可以實(shí)現(xiàn)深拷貝,但是當(dāng)遇到兩個互相引用的對象,會出現(xiàn)死循環(huán)的情況。
為了避免相互引用的對象導(dǎo)致死循環(huán)的情況,則應(yīng)該在遍歷的時候判斷是否相互引用對象,如果是則退出循環(huán)。
改進(jìn)版代碼如下:
function deepCopy(initialObj, finalObj) { let obj = finalObj || {}; for(let i in initialObj){ let prop = initialObj[i];//避免相互引用導(dǎo)致死循環(huán),如initialObj.a = initialObj的情況 if(prop === obj) { continue; } if(typeof prop === "object"){ obj[i] = (prop.constructor === Array) ? [] : {}; arguments.callee(prop, obj[i]); }else{ obj[i] = prop; } } return obj; } var foo = {a: 0, b: {c: 0}}; var str = {}; deepCopy(foo, str); str.a = 1; str.b.c = 1; console.log(str); //{a: 1, b: {c: 1}}; console.log(foo); //{a: 0, b: {c: 0}};
3.使用Object.create( )方法
function deepCopy(initialObj, finalObj){ let obj = finalObj || {}; for(let i in initialObj){ let prop = initialObj[i]; //避免相互引用對象導(dǎo)致死循環(huán),如initialObj[i].a = initialObj的情況 if(prop === obj){ continue; } if(typeof prop === "object"){ obj[i] = (prop.constructor === Array) ? [] : Object.create(prop); }else{ obj[i] = prop; } } return obj; } let foo = {a: 0, b: {c: 0}}; let str = {}; deepCopy(foo, str); str.a = 1; str.b.c = 1; console.log(str); //{a: 1, b: {c: 1}}; console.log(foo); //{a: 0, b: {c: 0}};
4.jQuery
jQuery提供了一個$.extend可以實(shí)現(xiàn)深拷貝
var $ = require("jquery); let foo = {a: 0, b: {c: 0}}; let str = $.extend(true, {}, foo); str.a = 1; str.b.c = 1; console.log(str); //{a: 1, b: {c: 1}}; console.log(foo); //{a: 0, b: {c: 0}};
5.lodash
另一個很熱門的函數(shù)庫lodash,也有提供_.cloneDeep用來深拷貝
var _ = require("lodash); let foo = {a: 0, b: {c: 0}}; let str = _.cloneDeep(foo); str.a = 1; str.b.c = 1; console.log(str); //{a: 1, b: {c: 1}}; console.log(foo); //{a: 0, b: {c: 0}};
局限性
所有深拷貝的方法并不適用于所有類型的對象。當(dāng)然還有其他的坑,像是如何拷貝原型鏈上的屬性?如何拷貝不可枚舉屬性等等。
雖然lodash是最安全的通用深拷貝方法,但如果你自己動手,可能會依據(jù)需求寫出最適合你的更高效的深拷貝的方法:
//適用于日期的簡單深拷貝的例子 function deepCopy(obj) { let copy; //處理三種簡單的引用數(shù)據(jù)類型加上undefined和null if(obj == null || typeof obj != "object") return obj; //處理Date if(obj instanceof Date){ copy = new Date(); copy.setTime(obj.getTime()); return copy; } //處理Array if(obj instanceof Array) { copy = []; for(let i = 0; i < obj.length; i++) { copy[i] = deepCopy(obj[i]); } return copy; } //處理Function if(obj instanceof Function) { copy = function() { return obj.apply(this, arguments); } return copy; } //處理Object if(obj instanceof Object) { copy = {}; for(let attr in obj) { if(obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]); } return copy; } throw new Error("無法深拷貝" +obj.constructor+ "類型的數(shù)據(jù)") }
快樂拷貝
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/99476.html
摘要:原文地址淺拷貝和深拷貝只針對像這樣的復(fù)雜對象的簡單來說,淺拷貝只拷貝一層對象的屬性,而深拷貝則遞歸拷貝了所有層級。淺拷貝通過來實(shí)現(xiàn)淺拷貝。 原文地址:http://www.silenceboy.com/201... 淺拷貝和深拷貝只針對像Object, Array這樣的復(fù)雜對象的.簡單來說,淺拷貝只拷貝一層對象的屬性,而深拷貝則遞歸拷貝了所有層級。 淺拷貝 通過 Object.ass...
摘要:所以,深拷貝是對對象以及對象的所有子對象進(jìn)行拷貝實(shí)現(xiàn)方式就是遞歸調(diào)用淺拷貝對于深拷貝的對象,改變源對象不會對得到的對象有影響。 為什么會有淺拷貝與深拷貝什么是淺拷貝與深拷貝如何實(shí)現(xiàn)淺拷貝與深拷貝好了,問題出來了,那么下面就讓我們帶著這幾個問題去探究一下吧! 如果文章中有出現(xiàn)紕漏、錯誤之處,還請看到的小伙伴多多指教,先行謝過 以下↓ 數(shù)據(jù)類型在開始了解 淺拷貝 與 深拷貝 之前,讓我們先...
摘要:所以,深拷貝是對對象以及對象的所有子對象進(jìn)行拷貝實(shí)現(xiàn)方式就是遞歸調(diào)用淺拷貝對于深拷貝的對象,改變源對象不會對得到的對象有影響。 上一篇 JavaScript中的繼承 前言 文章開始之前,讓我們先思考一下這幾個問題: 為什么會有淺拷貝與深拷貝 什么是淺拷貝與深拷貝 如何實(shí)現(xiàn)淺拷貝與深拷貝 好了,問題出來了,那么下面就讓我們帶著這幾個問題去探究一下吧! 如果文章中有出現(xiàn)紕漏、錯誤之處...
摘要:引用類型值引用類型值是保存在堆內(nèi)存中的對象,變量保存的只是指向該內(nèi)存的地址,在復(fù)制引用類型值的時候,其實(shí)只復(fù)制了指向該內(nèi)存的地址。 前言 要理解 JavaScript中淺拷貝和深拷貝的區(qū)別,首先要明白JavaScript的數(shù)據(jù)類型。JavaScript有兩種數(shù)據(jù)類型,基礎(chǔ)數(shù)據(jù)類型和引用數(shù)據(jù)類型。js的基本類型:undefined,null,string,boolean,number,s...
摘要:拷貝到,屬性均順利拷貝。大輝小輝,小輝,大輝小輝,小輝,大輝但是,若修改的屬性變?yōu)閷ο蠡驍?shù)組時,那么對象之間就會發(fā)生關(guān)聯(lián)。深拷貝不希望對象之間產(chǎn)生關(guān)聯(lián),那么這時候可以用到深拷貝。 淺拷貝 之前文章提到,在定義一個對象或數(shù)組時,變量存放的往往只是一個地址。當(dāng)我們對堆內(nèi)存中的對象復(fù)制時,如果屬性是對象或數(shù)組時,這時候我們拷貝的只是一個棧內(nèi)存的指針。因此b對象在訪問該屬性時,會根據(jù)指針尋找...
閱讀 3768·2021-09-02 15:11
閱讀 4774·2021-08-16 10:47
閱讀 1661·2019-08-29 18:35
閱讀 3180·2019-08-28 17:54
閱讀 2932·2019-08-26 11:37
閱讀 1574·2019-08-23 16:51
閱讀 1897·2019-08-23 14:36
閱讀 1879·2019-08-23 14:21