前端(vue)入門到精通課程:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調試工具:點擊使用
網上有很多關于深拷貝的文章,但是質量良莠不齊,有很多都考慮得不周到,寫的方法比較簡陋,難以令人滿意。本文旨在完成一個完美的深拷貝,大家看了如果有問題,歡迎一起補充完善。
評價一個深拷貝是否完善,請檢查以下問題是否都實現(xiàn)了:
-
基本類型數據
是否能拷貝? -
鍵和值都是基本類型的
普通對象
是否能拷貝? -
Symbol
作為對象的key是否能拷貝? -
Date
和RegExp
對象類型是否能拷貝? -
Map
和Set
對象類型是否能拷貝? -
Function
對象類型是否能拷貝?(函數我們一般不用深拷貝) -
對象的
原型
是否能拷貝? -
不可枚舉屬性
是否能拷貝? -
循環(huán)引用
是否能拷貝?
怎樣?你寫的深拷貝夠完善嗎?
深拷貝的最終實現(xiàn)
這里先直接給出最終的代碼版本,方便想快速了解的人查看,當然,你想一步步了解可以繼續(xù)查看文章余下的內容:
function deepClone(target) { const map = new WeakMap() function isObject(target) { return (typeof target === 'object' && target ) || typeof target === 'function' } function clone(data) { if (!isObject(data)) { return data } if ([Date, RegExp].includes(data.constructor)) { return new data.constructor(data) } if (typeof data === 'function') { return new Function('return ' + data.toString())() } const exist = map.get(data) if (exist) { return exist } if (data instanceof Map) { const result = new Map() map.set(data, result) data.forEach((val, key) => { if (isObject(val)) { result.set(key, clone(val)) } else { result.set(key, val) } }) return result } if (data instanceof Set) { const result = new Set() map.set(data, result) data.forEach(val => { if (isObject(val)) { result.add(clone(val)) } else { result.add(val) } }) return result } const keys = Reflect.ownKeys(data) const allDesc = Object.getOwnPropertyDescriptors(data) const result = Object.create(Object.getPrototypeOf(data), allDesc) map.set(data, result) keys.forEach(key => { const val = data[key] if (isObject(val)) { result[key] = clone(val) } else { result[key] = val } }) return result } return clone(target) }
1. JavaScript數據類型的拷貝原理
先看看JS數據類型圖(除了Object
,其他都是基礎類型):
在JavaScript中,基礎類型值的復制是直接拷貝一份新的一模一樣的數據,這兩份數據相互獨立,互不影響。而引用類型值(Object類型)的復制是傳遞對象的引用(也就是對象所在的內存地址,即指向對象的指針),相當于多個變量指向同一個對象,那么只要其中的一個變量對這個對象進行修改,其他的變量所指向的對象也會跟著修改(因為它們指向的是同一個對象)。如下圖:
2. 深淺拷貝
深淺拷貝主要針對的是Object類型,基礎類型的值本身即是復制一模一樣的一份,不區(qū)分深淺拷貝。這里我們先給出測試的拷貝對象,大家可以拿這個obj
對象來測試一下自己寫的深拷貝函數是否完善:
// 測試的obj對象 const obj = { // =========== 1.基礎數據類型 =========== num: 0, // number str: '', // string bool: true, // boolean unf: undefined, // undefined nul: null, // null sym: Symbol('sym'), // symbol bign: BigInt(1n), // bigint // =========== 2.Object類型 =========== // 普通對象 obj: { name: '我是一個對象', id: 1 }, // 數組 arr: [0, 1, 2], // 函數 func: function () { console.log('我是一個函數') }, // 日期 date: new Date(0), // 正則 reg: new RegExp('/我是一個正則/ig'), // Map map: new Map().set('mapKey', 1), // Set set: new Set().add('set'), // =========== 3.其他 =========== [Symbol('1')]: 1 // Symbol作為key }; // 4.添加不可枚舉屬性 Object.defineProperty(obj, 'innumerable', { enumerable: false, value: '不可枚舉屬性' }); // 5.設置原型對象 Object.setPrototypeOf(obj, { proto: 'proto' }) // 6.設置loop成循環(huán)引用的屬性 obj.loop = obj
obj
對象在Chrome瀏覽器中的結果:
2.1 淺拷貝
淺拷貝: 創(chuàng)建一個新的對象,來接受你要重新復制或引用的對象值。如果對象屬性是基本的數據類型,復制的就是基本類型的值給新對象;但如果屬性是引用數據類型,復制的就是內存中的地址,如果其中一個對象改變了這個內存中的地址所指向的對象,肯定會影響到另一個對象。
首先我們看看一些淺拷貝的方法(詳細了解可點擊對應方法的超鏈接):
方法 | 使用方式 | 注意事項 |
---|---|---|
Object.assign() | Object.assign(target, ...sources) 說明:用于將所有可枚舉屬性的值從一個或多個源對象分配到目標對象。它將返回目標對象。 |
1.不會拷貝對象的繼承屬性; 2.不會拷貝對象的不可枚舉的屬性; 3.可以拷貝 Symbol 類型的屬性。 |
展開語法 | let objClone = { ...obj }; |
缺陷和Object.assign() 差不多,但是如果屬性都是基本類型的值,使用擴展運算符進行淺拷貝會更加方便。 |
Array.prototype.concat()拷貝數組 | const new_array = old_array.concat(value1[, value2[, ...[, valueN]]]) |
淺拷貝,適用于基本類型值的數組 |
Array.prototype.slice()拷貝數組 | arr.slice([begin[, end]]) |
淺拷貝,適用于基本類型值的數組 |
這里只列舉了常用的幾種方式,除此之外當然還有其他