Jinwen Xie

一边工作,一边学习;写写代码,看看书,追追剧,走走世界!

Javascript中的深浅拷贝

18 Apr 2020 »

JS 中的数据类型分为两种,基本数据类型和引用数据类型,基本数据类型是保存在栈的数据结构中的,是按值访问,所以不存在深浅拷贝问题。
而比如对象,数组,函数,正则,时间对象这些都是引用数据类型,是保存在堆中的。所以,引用数据类型的复制,是内存地址的传递,并没有拷贝出一份新的数据。
操作拷贝之后的数据不会影响到原数据的值拷贝,就是深拷贝,反正,有影响则为浅拷贝。

浅拷贝

思路很简单,遍历对象,然后把属性和属性值都放在一个新的对象就OK了

// 浅拷贝
let obj = {
	name: 'xiejinwen',
	age: 26,
	family: {
	  son: 'gavin'
	}
}

function shallowCopy(obj) {
	if (typeof obj != 'object') return
	// instanceof 判断一个实例是否属于某种类型
	let newObj = obj instanceof Array ? [] : {}
	for (let key in obj) {
	  // hasOwnProperty 这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。
	  if (obj.hasOwnProperty(key)) {
	    newObj[key] = obj[key]
	  }
	}
	return newObj
}

let newShallowObj = shallowCopy(obj)
newShallowObj.name = 'gavin'
newShallowObj.sex.value = '女'

console.log(newShallowObj)

	// obj = {
	//   name: 'gavin', // 改变
	//   age: 26,
	//   sex: {
	//     value: '女' // 改变
	//   }
	// }

console.log(obj)

	// obj = {
	//   name: 'xiejinwen', // 第一层属性是有遍历的,所以没有改变
	//   age: 26,
	//   sex: {
	//     value: '女' // 第二层属性是没有遍历的,所以随着newShallowObj.sex.value的改变而改变
	//   }
	// }

从上述代码中,我们能够得出浅拷贝不是直接赋值,浅拷贝新建了一个对象,然后将源对象的属性都一一复制过来,复制的是值,而不是引用。
我们知道,对象都是按地址引用进行访问的,浅拷贝的复制只复制了第一层的属性,并没有递归将所有的值复制过来,所以,操作拷贝数据,对原数据产生了影响,故而为浅拷贝。
其实,那些直接返回原数组的方法可以简单实现数组和对象浅拷贝。

// 1、 数组浅拷贝 - slice
function shallowCopy(obj) {
    return obj.slice();
}

// 2、 数组浅拷贝 - concat
function shallowCopy(obj){
    return obj.concat();
}

// 3、 数组浅拷贝 - 遍历
function shallowCopy(obj){
  var result = [];
  for(var i = 0; i < obj.length; i++) {
    result.push(obj[i]);
  }
  return result;
}


// 4、 对象浅拷贝 - Object.assign
function shallowCopy(obj) {
    return Object.assign({},obj)
}

深拷贝

思路也很简单,就是在拷贝的时候判断一下属性值的类型,如果是对象,就递归调用深浅拷贝函数就ok了

let obj = {
  name: 'xiejinwen',
  age: 26,
  sex: {
    value: '男'
  }
}

function deepCopy(obj) {
  if (typeof obj != 'object') return
  let newObj = obj instanceof Array ? [] : {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
	  // 如果是对象,就递归调用深浅拷贝函数	
      newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
    }
  }
  return newObj
}

let newDeepObj = deepCopy(obj)
newDeepObj.name = 'gavin'
newDeepObj.sex.value = '女'

console.log(newDeepObj)

  // obj = {
  //   name: 'gavin', // 改变
  //   age: 26,
  //   sex: {
  //     value: '女' // 改变
  //   }
  // }

console.log(obj)

  // obj = {
  //   name: 'xiejinwen', // 第一层属性是有遍历的,所以没有改变
  //   age: 26,
  //   sex: {
  //     value: '男' // 第二层属性有递归遍历,所以随着newShallowObj.sex.value的改变也不会改变
  //   }
  // }