JS进阶之深浅拷贝

161 阅读4分钟

   携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情 >>

深浅拷贝是什么

学习深浅拷贝前得先了解 js 的两种数据类型:

  1. 基本类型:String、Number 等,在使用时,会被放到栈中。当有新值时就会开辟新的内存空间。

  2. 引用类型:Array、Object 等,在使用时,会被放到堆中。当有新的对象和数组时 js 就会在堆中开辟新的内存地址,这个地址存在指针(引用)与对象(值)。

简述:

在复制基本类型的值时,js 会直接将栈中的数据取出复制给新值,并开辟出一个新的内存空间用来存储新的被复制的值。

而复制引用类型时,复制对象仅仅只会复制被复制对象的指针,也就是说新复制的对象并没有得到一个新的内存地址,其指针,也就是引用还是指向被复制的对象。

一、浅拷贝(Shallow copy)

复制对象与被复制对象使用的还是同一个内存地址上的值。

二、深拷贝(Deep copy)

复制对象与被复制对象使用的不是同一个内存地址中的对象,在复制对象复制被复制对象时 js 会在堆中新开辟一个内存地址用来存储复制对象的指针。

三、主要区别:

A 复制了 B 对象,在修改 B 对象时,如果 A 对象的值也被修改了,这就是浅拷贝,反之 A 的值没有被修改,那就是深拷贝。

1. 浅拷贝实例:

// 对象1
let shallowCopy1 = {
    id: 1,
    age: 18,
    sex: '男',
    name: '张三'
}
// 对象2 复制 对象1
let shallowCopy2 = shallowCopy1
// 修改 对象2
shallowCopy2.age = 19
// 对象 1, 2 输出内容一致
console.log(shallowCopy1)
console.log(shallowCopy2)

输出内容一致:

2. 深拷贝实例:

// 对象1
let deepCopy1 = {
    name: '李四',
    age: '男'
}
// 使用 JSON.parse(JSON.stringify(Object)) 深拷贝对象 1
let deepCopy2 = JSON.parse(JSON.stringify(deepCopy1))
// 修改 对象2
deepCopy2.name = '王五'
// 对象 1, 2 输出内容不一致
console.log(deepCopy1)
console.log(deepCopy2)

3. 浅拷贝实现方式总结:

1. Object.assign()

// 对象 1
let shallowCopy1 = { 
  person: {
    name: "kobe", 
    age: 41
  },
  sports:'basketball' 
}
// 使用 Obje.assign 方法浅拷贝对象 1
let shallowCopy2 = Object.assign({}, shallowCopy1)
// 修改对象 2
shallowCopy2.person.name = "wade"

2. 展开运算符

就代码量来说,需要浅拷贝的时候,使用展开运算符是最好的。

// 对象 1
let shallowCopy1 = { 
  name: 'Kobe', 
  address:{
    x:100,
    y:100
  }
}
// 使用展开运算符浅拷贝对象1
let shallowCopy2= {... shallowCopy1}

3. 数组的 .concat() 合并方法

// 数组 1
let shallowCopy1 = [1, 3, {
    	username: 'kobe'
    }]
// 使用数组合并方法浅拷贝数组 1
let shallowCopy2 = shallowCopy1.concat()

4. 数组的 .slice() 方法

// 数组1
let shallowCopy1 = [1, 3, {
    username: ' kobe'
    }]
// 使用数组 array.slice() 方法浅拷贝数组 1
let shallowCopy2 = shallowCopy1.slice()

5. 使用 Array.from 实现

// 数组1
let shallowCopy1 = [1, 3, {
    username: ' kobe'
    }]
// 使用数组 array.slice() 方法浅拷贝数组 1
let shallowCopy2 = Array.from(shallowCopy1)

4. 深拷贝实现方式总结:

1. 使用 JSON.parse(JSON.stringify(Object))

实现:先将数据转为 JSON 字符串,后又转回 js 对象。缺点是不能转为函数和正则。

// 对象 1
let deepCopy1 = { 
  person: {
    name: "kobe", 
    age: 41
  },
  sports:'basketball' 
}
// 使用 json 转换对象深拷贝对象 1
let deepCopy2 = JSON.parse(JSON.stringify(deepCopy1))
// 修改对象 2
deepCopy2.person.name = "wade"

2. 使用递归

实现:使用递归将数组和对象中的每一项都遍历出来,再去把值复制出来,也是深拷贝的一种实现方式。缺点也很明显,代码量太大了。而且需要用到递归。

function deepClone(obj){
	let objClone =  Array.isArray(obj) ? [] : {};
	if (obj && typeof obj === 'object') {
		for(let key in obj){
			//判断对象的这条属性是否为对象
			if (obj[key] && typeof obj[key] === 'object'){
			  //若是对象进行嵌套调用
				objClone[key] = deepClone(obj[key]);
			}else{
				objClone[key] = obj[key]
			}
		}
	}
	//返回深度克隆后的对象
	return objClone; 
}

3. 使用 jQuery 中的 .extend() 方法

// 对象1
var deepCopy1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
}
// 使用 jQuery.extend() 复制对象1
var deepCopy2 = $.extend(true, {}, deepCopy1)
// 输出:false
console.log(deepCopy1.b.f === deepCopy2.b.f)

4. 使用 for...in

function deepClone(obj) {
	var result = {}
	if (obj && typeof obj === 'object') {
		for (let key in obj) {
			if (obj[key] && typeof obj[key] === 'object') {
				// 如果对象的属性值为 object ,就递归调用 deepClone,即把某个值对象复制一份到新的对象中
				result[key] = deepClone(obj[key]);
			} else {
                                // 如果对象的属性值不为 object,就直接复制键值对到新的对象当中
				result[key] = obj[key];
			}
		}
		return result;
	}
	return obj;
}