欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

超詳細(xì)JavaScript深淺拷貝的實現(xiàn)教程

 更新時間:2022年09月26日 09:34:47   作者:CUGGZ  
淺拷貝是指,一個新的對象對原始對象的屬性值進(jìn)行精確地拷貝;深拷貝是指,對于簡單數(shù)據(jù)類型直接拷貝他的值,對于引用數(shù)據(jù)類型,在堆內(nèi)存中開辟一塊內(nèi)存用于存放復(fù)制的對象,并把原有的對象類型數(shù)據(jù)拷貝過來。本文將實現(xiàn)JavaScript深淺拷貝,需要的可以參考一下

一、淺拷貝

淺拷貝是指,一個新的對象對原始對象的屬性值進(jìn)行精確地拷貝,如果拷貝的是基本數(shù)據(jù)類型,拷貝的就是基本數(shù)據(jù)類型的值;如果拷貝的是引用數(shù)據(jù)類型,拷貝的就是內(nèi)存地址。如果其中一個對象的引用內(nèi)存地址發(fā)生改變,另一個對象也會發(fā)生變化。

1. Object.assign()

object.assign 是 ES6 中 object 的一個方法,該方法可以用于 JS 對象的合并。我們可以使用它來實現(xiàn)淺拷貝。

該方法的參數(shù) target 指的是目標(biāo)對象,sources指的是源對象。使用形式如下:

Object.assign(target,?...sources)

使用示例:

let?target?=?{a:?1};
let?object2?=?{b:?{d?:?2}};
let?object3?=?{c:?3};
Object.assign(target,?object2,?object3);??
console.log(target);??//?{a:?1,?b:?{d?:?2},?c:?3}

這里通過 Object.assign 將 object2 和 object3 拷貝到了 target 對象中,下面來嘗試將 object2 對象中的 b 屬性中的d屬性由 2 修改為 666:

object2.b.d?=?666;
console.log(target);?//?{a:?1,?b:?{d:?666},?c:?3}

可以看到,target的b屬性值的d屬性值變成了666,因為這個b的屬性值是一個對象,它保存了該對象的內(nèi)存地址,當(dāng)原對象發(fā)生變化時,引用他的值也會發(fā)生變化。

注意:

  • 如果目標(biāo)對象和源對象有同名屬性,或者多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性;
  • 如果該函數(shù)只有一個參數(shù),當(dāng)參數(shù)為對象時,直接返回該對象;當(dāng)參數(shù)不是對象時,會先將參數(shù)轉(zhuǎn)為對象然后返回;
  • 因為null 和 undefined 不能轉(zhuǎn)化為對象,所以第一個參數(shù)不能為null或 undefined,否則會報錯;
  • 它不會拷貝對象的繼承屬性,不會拷貝對象的不可枚舉的屬性,可以拷貝 Symbol 類型的屬性。

實際上,Object.assign 會循環(huán)遍歷原對象的可枚舉屬性,通過復(fù)制的方式將其賦值給目標(biāo)對象的相應(yīng)屬性。

2. 擴(kuò)展運算符

使用擴(kuò)展運算符可以在構(gòu)造字面量對象的時候,進(jìn)行屬性的拷貝。使用形式如下:

let?cloneObj?=?{?...obj?};

使用示例:

let?obj1?=?{a:1,b:{c:1}}
let?obj2?=?{...obj1};
obj1.a?=?2;
console.log(obj1);?//{a:2,b:{c:1}}
console.log(obj2);?//{a:1,b:{c:1}}
obj1.b.c?=?2;
console.log(obj1);?//{a:2,b:{c:2}}
console.log(obj2);?//{a:1,b:{c:2}}

擴(kuò)展運算符 和 object.assign 實現(xiàn)的淺拷貝的功能差不多,如果屬性都是基本類型的值,使用擴(kuò)展運算符進(jìn)行淺拷貝會更加方便。

3. 數(shù)組淺拷貝

(1)Array.prototype.slice()

slice()方法是JavaScript數(shù)組方法,該方法可以從已有數(shù)組中返回選定的元素,不會改變原始數(shù)組。使用方式如下:

array.slice(start,?end)

該方法有兩個參數(shù),兩個參數(shù)都可選:

  • start: 規(guī)定從何處開始選取。如果是負(fù)數(shù),那么它規(guī)定從數(shù)組尾部開始算起的位置。也就是說,-1 指最后一個元素,-2 指倒數(shù)第二個元素,以此類推。
  • end:規(guī)定從何處結(jié)束選取。該參數(shù)是數(shù)組片斷結(jié)束處的數(shù)組下標(biāo)。如果沒有指定該參數(shù),那么切分的數(shù)組包含從 start 到數(shù)組結(jié)束的所有元素。如果這個參數(shù)是負(fù)數(shù),那么它規(guī)定的是從數(shù)組尾部開始算起的元素。

如果兩個參數(shù)都不寫,就可以實現(xiàn)一個數(shù)組的淺拷貝:

let?arr?=?[1,2,3,4];
console.log(arr.slice());?//?[1,2,3,4]
console.log(arr.slice()?===?arr);?//false

slice 方法不會修改原數(shù)組,只會返回一個淺拷貝了原數(shù)組中的元素的一個新數(shù)組。原數(shù)組的元素會按照下述規(guī)則拷貝:

  • 如果該元素是個對象引用 (不是實際的對象),slice 會拷貝這個對象引用到新的數(shù)組里。兩個對象引用都引用了同一個對象。如果被引用的對象發(fā)生改變,則新的和原來的數(shù)組中的這個元素也會發(fā)生改變。
  • 對于字符串、數(shù)字及布爾值來說,slice 會拷貝這些值到新的數(shù)組里。在別的數(shù)組里修改這些字符串或數(shù)字或是布爾值,將不會影響另一個數(shù)組。

如果向兩個數(shù)組任一中添加了新元素,則另一個不會受到影響。

(2)Array.prototype.concat()

concat() 方法用于合并兩個或多個數(shù)組,此方法不會更改原始數(shù)組,而是返回一個新數(shù)組。使用方式如下:

arrayObject.concat(arrayX,arrayX,......,arrayX)

該方法的參數(shù)arrayX是一個數(shù)組或值,將被合并到arrayObject數(shù)組中。如果省略了所有 arrayX 參數(shù),則 concat 會返回調(diào)用此方法的現(xiàn)存數(shù)組的一個淺拷貝:

let?arr?=?[1,2,3,4];
console.log(arr.concat());?//?[1,2,3,4]
console.log(arr.concat()?===?arr);?//false

concat方法創(chuàng)建一個新的數(shù)組,它由被調(diào)用的對象中的元素組成,每個參數(shù)的順序依次是該參數(shù)的元素(參數(shù)是數(shù)組)或參數(shù)本身(參數(shù)不是數(shù)組)。它不會遞歸到嵌套數(shù)組參數(shù)中。

concat方法不會改變this或任何作為參數(shù)提供的數(shù)組,而是返回一個淺拷貝,它包含與原始數(shù)組相結(jié)合的相同元素的副本。原始數(shù)組的元素將復(fù)制到新數(shù)組中,如下所示:

  • 對象引用(而不是實際對象):concat將對象引用復(fù)制到新數(shù)組中。原始數(shù)組和新數(shù)組都引用相同的對象。也就是說,如果引用的對象被修改,則更改對于新數(shù)組和原始數(shù)組都是可見的。這包括也是數(shù)組的數(shù)組參數(shù)的元素。
  • 數(shù)據(jù)類型如字符串,數(shù)字和布爾值:concat將字符串和數(shù)字的值復(fù)制到新數(shù)組中。

4. 手寫實現(xiàn)淺拷貝

根據(jù)以上對淺拷貝的理解,實現(xiàn)淺拷貝的思路:

  • 對基礎(chǔ)類型做最基本的拷貝;
  • 對引用類型開辟新的存儲,并且拷貝一層對象屬性。

代碼實現(xiàn):

//?淺拷貝的實現(xiàn);
function?shallowCopy(object)?{
??//?只拷貝對象
??if?(!object?||?typeof?object?!==?"object")?return;
??//?根據(jù)?object?的類型判斷是新建一個數(shù)組還是對象
??let?newObject?=?Array.isArray(object)???[]?:?{};
??//?遍歷?object,并且判斷是?object?的屬性才拷貝
??for?(let?key?in?object)?{
????if?(object.hasOwnProperty(key))?{
??????newObject[key]?=?object[key];
????}
??}
??return?newObject;
}

這里用到了 hasOwnProperty() 方法,該方法會返回一個布爾值,指示對象自身屬性中是否具有指定的屬性。所有繼承了 Object 的對象都會繼承到 hasOwnProperty() 方法。這個方法可以用來檢測一個對象是否是自身屬性。

可以看到,所有的淺拷貝都只能拷貝一層對象。如果存在對象的嵌套,那么淺拷貝就無能為力了。深拷貝就是為了解決這個問題而生的,它能解決多層對象嵌套問題,徹底實現(xiàn)拷貝。

二、深拷貝

深拷貝是指,對于簡單數(shù)據(jù)類型直接拷貝他的值,對于引用數(shù)據(jù)類型,在堆內(nèi)存中開辟一塊內(nèi)存用于存放復(fù)制的對象,并把原有的對象類型數(shù)據(jù)拷貝過來,這兩個對象相互獨立,屬于兩個不同的內(nèi)存地址,修改其中一個,另一個不會發(fā)生改變。

1. JSON.stringify()

JSON.parse(JSON.stringify(obj))是比較常用的深拷貝方法之一,它的原理就是利用JSON.stringify 將JavaScript對象序列化成為JSON字符串),并將對象里面的內(nèi)容轉(zhuǎn)換成字符串,再使用JSON.parse來反序列化,將字符串生成一個新的JavaScript對象。

這個方法是目前我在公司項目開發(fā)中使用最多的深拷貝的方法,也是最簡單的方法。

使用示例:

let?obj1?=?{??
??a:?0,
??b:?{
????c:?0
??}
};
let?obj2?=?JSON.parse(JSON.stringify(obj1));
obj1.a?=?1;
obj1.b.c?=?1;
console.log(obj1);?//?{a:?1,?b:?{c:?1}}
console.log(obj2);?//?{a:?0,?b:?{c:?0}}

這個方法雖然簡單粗暴,但也存在一些問題,在使用該方法時需要注意:

  • 拷貝的對象中如果有函數(shù),undefined,symbol,當(dāng)使用過JSON.stringify()進(jìn)行處理之后,都會消失。
  • 無法拷貝不可枚舉的屬性;
  • 無法拷貝對象的原型鏈;
  • 拷貝 Date 引用類型會變成字符串;
  • 拷貝 RegExp 引用類型會變成空對象;
  • 對象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的結(jié)果會變成 null;
  • 無法拷貝對象的循環(huán)應(yīng)用,即對象成環(huán) (obj[key] = obj)。

在日常開發(fā)中,上述幾種情況一般很少出現(xiàn),所以這種方法基本可以滿足日常的開發(fā)需求。如果需要拷貝的對象中存在上述情況,還是要考慮使用下面的幾種方法。

2. 函數(shù)庫lodash

該函數(shù)庫也有提供_.cloneDeep用來做深拷貝,可以直接引入并使用:

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

這里附上lodash中深拷貝的源代碼供大家學(xué)習(xí):

/**
* value:需要拷貝的對象
* bitmask:位掩碼,其中 1 是深拷貝,2 拷貝原型鏈上的屬性,4 是拷貝 Symbols 屬性
* customizer:定制的 clone 函數(shù)
* key:傳入 value 值的 key
* object:傳入 value 值的父對象
* stack:Stack 棧,用來處理循環(huán)引用
*/

function?baseClone(value,?bitmask,?customizer,?key,?object,?stack)?{
????let?result
?
????//?標(biāo)志位
????const?isDeep?=?bitmask?&?CLONE_DEEP_FLAG??//?深拷貝,true
????const?isFlat?=?bitmask?&?CLONE_FLAT_FLAG??//?拷貝原型鏈,false
????const?isFull?=?bitmask?&?CLONE_SYMBOLS_FLAG?//?拷貝?Symbol,true
?
????//?自定義?clone?函數(shù)
????if?(customizer)?{
????????result?=?object???customizer(value,?key,?object,?stack)?:?customizer(value)
????}
????if?(result?!==?undefined)?{
????????return?result
????}
?
????//?非對象??
????if?(!isObject(value))?{
????????return?value
????}
????
????const?isArr?=?Array.isArray(value)
????const?tag?=?getTag(value)
????if?(isArr)?{
????????//?數(shù)組
????????result?=?initCloneArray(value)
????????if?(!isDeep)?{
????????????return?copyArray(value,?result)
????????}
????}?else?{
????????//?對象
????????const?isFunc?=?typeof?value?==?'function'
?
????????if?(isBuffer(value))?{
????????????return?cloneBuffer(value,?isDeep)
????????}
????????if?(tag?==?objectTag?||?tag?==?argsTag?||?(isFunc?&&?!object))?{
????????????result?=?(isFlat?||?isFunc)???{}?:?initCloneObject(value)
????????????if?(!isDeep)?{
????????????????return?isFlat
??????????????????????copySymbolsIn(value,?copyObject(value,?keysIn(value),?result))
?????????????????:?copySymbols(value,?Object.assign(result,?value))
????????????}
????????}?else?{
????????????if?(isFunc?||?!cloneableTags[tag])?{
????????????????return?object???value?:?{}
????????????}
????????????result?=?initCloneByTag(value,?tag,?isDeep)
????????}
????}
????//?循環(huán)引用
????stack?||?(stack?=?new?Stack)
????const?stacked?=?stack.get(value)
????if?(stacked)?{
????????return?stacked
????}
????stack.set(value,?result)
?
????//?Map
????if?(tag?==?mapTag)?{
????????value.forEach((subValue,?key)?=>?{
????????????result.set(key,?baseClone(subValue,?bitmask,?customizer,?key,?value,?stack))
????????})
????????return?result
????}
?
????//?Set
????if?(tag?==?setTag)?{
????????value.forEach((subValue)?=>?{
????????????result.add(baseClone(subValue,?bitmask,?customizer,?subValue,?value,?stack))
????????})
????????return?result
????}
?
????//?TypedArray
????if?(isTypedArray(value))?{
????????return?result
????}
?
????//?Symbol?&?原型鏈
????const?keysFunc?=?isFull
???????(isFlat???getAllKeysIn?:?getAllKeys)
?????:?(isFlat???keysIn?:?keys)
?
????const?props?=?isArr???undefined?:?keysFunc(value)
????
????//?遍歷賦值
????arrayEach(props?||?value,?(subValue,?key)?=>?{
????????if?(props)?{
????????????key?=?subValue
????????????subValue?=?value[key]
????????}
????????assignValue(result,?key,?baseClone(subValue,?bitmask,?customizer,?key,?value,?stack))
????})
????
????//?返回結(jié)果
????return?result
}

3. 手寫實現(xiàn)深拷貝

(1)基礎(chǔ)遞歸實現(xiàn)

實現(xiàn)深拷貝的思路就是,使用for in來遍歷傳入?yún)?shù)的屬性值,如果值是基本類型就直接復(fù)制,如果是引用類型就進(jìn)行遞歸調(diào)用該函數(shù),實現(xiàn)代碼如下:

function?deepClone(source)?{
??????//判斷source是不是對象
??????if?(source?instanceof?Object?==?false)?return?source;
??????
????//根據(jù)source類型初始化結(jié)果變量
??????let?target?=?Array.isArray(source)???[]?:?{};?
??????for?(let?i?in?source)?{
????????//?判斷是否是自身屬性
????????if?(source.hasOwnProperty(i))?{
??????????//判斷數(shù)據(jù)i的類型
??????????if?(typeof?source[i]?===?'object')?{
????????????target[i]?=?deepClone(source[i]);
??????????}?else?{
????????????target[i]?=?source[i];
??????????}
????????}
??????}
??????return?target;
????}
???
console.log(clone({b:?{c:?{d:?1}}}));??//?{b:?{c:?{d:?1}}})

這樣雖然實現(xiàn)了深拷貝,但也存在一些問題:

  • 不能復(fù)制不可枚舉屬性以及 Symbol 類型;
  • 只能對普通引用類型的值做遞歸復(fù)制,對于 Date、RegExp、Function 等引用類型不能正確拷貝;
  • 可能存在循環(huán)引用問題。

(2)優(yōu)化遞歸實現(xiàn)

上面只是實現(xiàn)了一個基礎(chǔ)版的深拷貝,對于上面存在的幾個問題,可以嘗試去解決一下:

  • 使用 Reflect.ownKeys() 方法來解決不能復(fù)制不可枚舉屬性以及 Symbol 類型的問題。Reflect.ownKeys() 方法會返回一個由目標(biāo)對象自身的屬性鍵組成的數(shù)組。它的返回值等同于: Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target));
  • 當(dāng)參數(shù)值為 Date、RegExp 類型時,直接生成一個新的實例并返回;
  • 利用 Object.getOwnPropertyDescriptors() 方以獲得對象的所有屬性以及對應(yīng)的特性。簡單來說,這個方法返回給定對象的所有屬性的信息,包括有關(guān)getter和setter的信息。它允許創(chuàng)建對象的副本并在復(fù)制所有屬性(包括getter和setter)時克隆它。
  • 使用 Object.create() 方法創(chuàng)建一個新對象,并繼承傳入原對象的原型鏈。Object.create()方法會創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的__proto__。
  • 使用 WeakMap 類型作為 Hash 表,WeakMap 是弱引用類型,可以防止內(nèi)存泄漏,所以可以用來檢測循環(huán)引用,如果存在循環(huán),則引用直接返回 WeakMap 存儲的值。WeakMap的特性就是,保存在其中的對象不會影響垃圾回收,如果WeakMap保存的節(jié)點,在其他地方都沒有被引用了,那么即使它還在WeakMap中也會被垃圾回收回收掉了。在深拷貝的過程當(dāng)中,里面所有的引用對象都是被引用的,為了解決循環(huán)引用的問題,在深拷貝的過程中,希望有個數(shù)據(jù)結(jié)構(gòu)能夠記錄每個引用對象有沒有被使用過,但是深拷貝結(jié)束之后這個數(shù)據(jù)能自動被垃圾回收,避免內(nèi)存泄漏。

代碼實現(xiàn):

function?deepClone?(obj,?hash?=?new?WeakMap())?{
??//?日期對象直接返回一個新的日期對象
??if?(obj?instanceof?Date){
???return?new?Date(obj);
??}?
??//正則對象直接返回一個新的正則對象?????
??if?(obj?instanceof?RegExp){
???return?new?RegExp(obj);?????
??}
??//如果循環(huán)引用,就用?weakMap?來解決
??if?(hash.has(obj)){
???return?hash.get(obj);
??}
??//?獲取對象所有自身屬性的描述
??let?allDesc?=?Object.getOwnPropertyDescriptors(obj);
??//?遍歷傳入?yún)?shù)所有鍵的特性
??let?cloneObj?=?Object.create(Object.getPrototypeOf(obj),?allDesc)
??
??hash.set(obj,?cloneObj)
??for?(let?key?of?Reflect.ownKeys(obj))?{?
????if(typeof?obj[key]?===?'object'?&&?obj[key]?!==?null){
?????cloneObj[key]?=?deepClone(obj[key],?hash);
????}?else?{
?????cloneObj[key]?=?obj[key];
????}
??}
??return?cloneObj
}

可以使用以下數(shù)據(jù)進(jìn)行測試:

let?obj?=?{
??num:?1,
??str:?'str',
??boolean:?true,
??und:?undefined,
??nul:?null,
??obj:?{?name:?'對象',?id:?1?},
??arr:?[0,?1,?2],
??func:?function?()?{?console.log('函數(shù)')?},
??date:?new?Date(1),
??reg:?new?RegExp('/正則/ig'),
??[Symbol('1')]:?1,
};
Object.defineProperty(obj,?'innumerable',?{
??enumerable:?false,?value:?'不可枚舉屬性'?
});
obj?=?Object.create(obj,?Object.getOwnPropertyDescriptors(obj))
obj.loop?=?obj????//?將loop設(shè)置成循環(huán)引用的屬性
let?cloneObj?=?deepClone(obj)

console.log('obj',?obj)
console.log('cloneObj',?cloneObj)

運行結(jié)果如下:

可以看到,這樣基本就實現(xiàn)了多數(shù)數(shù)據(jù)類型的深拷貝,不過也還存在一些缺陷,比如Map和Set結(jié)構(gòu)在這個方法中無法進(jìn)行拷貝,可以自己實現(xiàn)一下。

以上就是超詳細(xì)JavaScript深淺拷貝的實現(xiàn)教程的詳細(xì)內(nèi)容,更多關(guān)于JavaScript深淺拷貝的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論