一、淺拷貝
淺拷貝只會將被復制對象的第一層屬性進行復制,若第一層屬性為原始類型的值,則直接復制其值,一般稱之為“傳值”;若第一層屬性為引用類型的值,則復制的是其存儲的指向堆內存對象的地址指針,一般稱之為“傳址”。因此淺拷貝的結果存在當改變一個對象的值時引起另一個對象值變化的問題。即新對象和舊對象之間值相互影響。下面是一些實現淺拷貝的方法:
(1)Array.concat(),Array.slice(),Array.from() 等
let a = [1, 2, 3, {name: 'Joy'}]; let b = [].concat(a); let c = b.slice(); let d = Array.from(c); a[1] = 1000; console.log('a: ', a); //a: [ 1, 1000, 3, { name: 'Joy' } ] console.log('b: ', b); //b: [ 1, 2, 3, { name: 'Joy' } ] console.log('c: ', c); //c: [ 1, 2, 3, { name: 'Joy' } ] console.log('d: ', d); //d: [ 1, 2, 3, { name: 'Joy' } ] /**---改變a中的原始類型值,b、c、d沒有變化 */ a[3].name = 'Bob'; console.log('a: ', a); //a: [ 1, 1000, 3, { name: 'Bob' } ] console.log('b: ', b); //b: [ 1, 2, 3, { name: 'Bob' } ] console.log('c: ', c); //c: [ 1, 2, 3, { name: 'Bob' } ] console.log('d: ', d); //d: [ 1, 2, 3, { name: 'Bob' } ] /**---改變a中的引用類型值,發現b、c、d中引用類型值也發生了變化 */
(2)Object.assign()
let a = { name: 'Joy', friends: ['Bob', 'Tom', 'Jim'] }; let b = Object.assign({}, a); a.name = 'Lie'; console.log('a: ', a); //a: { name: 'Lie', friends: [ 'Bob', 'Tom', 'Jim' ] } console.log('b: ', b); //b: { name: 'Joy', friends: [ 'Bob', 'Tom', 'Jim' ] } /**---改變a中的原始類型值,b沒有變化 */ a.friends.push('Sun'); console.log('a: ', a); //a: { name: 'Lie', friends: [ 'Bob', 'Tom', 'Jim', 'Sun' ] } console.log('b: ', b); //b: { name: 'Joy', friends: [ 'Bob', 'Tom', 'Jim', 'Sun' ] } /**---改變a中的引用類型值,發現b中引用類型值也發生了變化 */
還有例如 es6擴展運算符 等方法也實現的是淺拷貝。
二、深拷貝
而不同於淺拷貝,深拷貝是逐層對目標對象進行復制,意味着會在棧內存中重新分配空間存儲指向一個新對象的新地址指針,因此不存在改變一個對象值而引發另一個對象隨之改變的問題。
1、使用遞歸的方式實現深拷貝
//使用遞歸的方式實現數組、對象的深拷貝 function deepClone1(obj) { //判斷拷貝的要進行深拷貝的是數組還是對象,是數組的話進行數組拷貝,對象的話進行對象拷貝 var objClone = Array.isArray(obj) ? [] : {}; //進行深拷貝的不能為空,並且是對象或者是 if (obj && typeof obj === "object") { for (key in obj) { if (obj.hasOwnProperty(key)) { if (obj[key] && typeof obj[key] === "object") { objClone[key] = deepClone1(obj[key]); } else { objClone[key] = obj[key]; } } } } return objClone; }
2、通過 JSON 對象實現深拷貝
//通過js的內置對象JSON來進行數組對象的深拷貝 function deepClone2(obj) { var _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone; }
問題:無法實現對對象中方法的深拷貝
3、通過jQuery的extend方法實現深拷貝
var array = [1,2,3,4]; var newArray = $.extend(true,[],array);
4、Object.assign()拷貝
當對象中只有一級屬性,沒有二級屬性的時候,此方法為深拷貝,但是對象中有對象的時候,此方法,在二級屬性以后就是淺拷貝
5、lodash函數庫實現深拷貝
lodash很熱門的函數庫,提供了 lodash.cloneDeep()實現深拷貝
三、問題:包含處理循環引用問題的深拷貝
循環引用問題的產生原因可能是對象之間相互引用,也可能是對象引用了其自身,而造成死循環的原因則是我們在進行深拷貝時並沒有將這種引用情況考慮進去,因此解決問題的關鍵也就是可以將這些引用存儲起來並在發現引用時返回被引用過的對象,從而結束遞歸的調用。
/** * js深拷貝(包括 循環引用 的情況) * * @param {*} originObj * @param {*} [map=new WeakMap()] 使用hash表記錄所有的對象的引用關系,初始化為空 * @returns */ function deepClone( originObj, map = new WeakMap() ) { if(!originObj || typeof originObj !== 'object') return originObj; //空或者非對象則返回本身 //如果這個對象已經被記錄則直接返回 if( map.get(originObj) ) { return map.get(originObj); } //這個對象還沒有被記錄,將其引用記錄在map中,進行拷貝 let result = Array.isArray(originObj) ? [] : {}; //拷貝結果 map.set(originObj, result); //記錄引用關系 let keys = Object.keys(originObj); //originObj的全部key集合 //拷貝 for(let i =0,len=keys.length; i<len; i++) { let key = keys[i]; let temp = originObj[key]; result[key] = deepClone(temp, map); } return result; }
例子:
/**例子1: 數組深拷貝 */ let a = [1, 2]; let b = [4, 5, 6, a]; a.push(b); let c = deepClone(a); a.push(200); b.push(10); console.log(a); //[ 1, 2, [ 4, 5, 6, [Circular], 10 ], 200 ] console.log(b); //[ 4, 5, 6, [ 1, 2, [Circular], 200 ], 10 ] console.log(c); //[ 1, 2, [ 4, 5, 6, [Circular] ] ] /**a 和 b均變化,因為a、b互為直接引用, 而通過深拷貝,c不受a、b變化的影響 */ c[2][0] = 100; console.log(a); //[ 1, 2, [ 4, 5, 6, [Circular], 10 ], 200 ] console.log(b); //[ 4, 5, 6, [ 1, 2, [Circular], 200 ], 10 ] console.log(c); //[ 1, 2, [ 100, 5, 6, [Circular] ] ] /**深拷貝,c變化,而a和b未發生變化 */
參考:https://blog.csdn.net/chentony123/article/details/81428803
參考:https://blog.csdn.net/Snoopyqiuer/article/details/101106303