前言
手搓深浅拷贝是面试官的常考题,在上一篇的学习中我们深度学习了拷贝,熟悉运用了深浅拷贝的方法。在本文中我们将利用拷贝的原理手搓一个函数来实现拷贝的功能。
浅拷贝的实现原理
如何实现浅拷贝?
首先我们要知道通俗来讲拷贝就是将一个对象中的属性全部复制到另一个集合里面。全部复制--那就需要遍历。在数组中,我们可以轻松的利用for()进行遍历,原理是for能使用计时器迭代每一个数组的索引位置(就是从0开始的下标) 。但是在对象中,并不存在下标值。所以我们需要用新的循环方法for...in来遍历对象。 我们需要先熟悉for...in的用法 对于对象obj来举例:
js
let obj ={
a:1,
b:2,
c:3
}
我们通过for(let key in obj)
可以访问obj
的所有可枚举属性。let key
会声明一个新的块级作用域的变量 key
,用于在每次迭代中存储属性的属性名。我们同样可以通过obj[key]
获取到属性的值。 通过:
js
for (let key in obj) {
console.log(key, obj[key]);
}
得到:
遍历对象是拷贝的第一步通过遍历对象得知对象中的所有属性后我们需要将这些属性放入到一个新的集合中,所以我们let newObj = {}
这个集合就是拷贝后的对象。
现在我们进行浅拷贝的核心操作:将引用地址复制。如何实现?仔细想想,复制引用地址不就是将原来的属性值放到新对象中吗,正好用上我们刚学习方法:newObj[key] = obj[key]
--这样就满足了"当原对象改变时,复制的对象也会改变"的浅拷贝规则,这段代码也是浅拷贝的根本原理。
还需要考虑到的是:在拷贝时绝大多数情况下希望拷贝的往往是显示属性,不希望复制对象隐式具有的属性,所以对象的隐式原型一般都不拷贝。但是for..in语句会遍历对象身上具有的显示属性和隐式属性,如何屏蔽掉对象的隐式属性呢?我们需要一个判断,运用obj.hasOwnProperty(key)
方法可以解决这个问题。
obj.hasOwnProperty(key)
方法可以判断对象身上的属性是隐式还是显性的--当对象obj中的属性key为显性时,返回true;当为隐性时,返回false。结合if判断,最终只保留显示属性进行拷贝。
完整代码如下:
js
let obj = {
name: 'Tom',
like: {
a: 'food'
}
}
function shallow(obj) {
let newObj = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
小结一下
- 借助for in 遍历原对象,将原对象的属性值增加到新对象中,
- 因为for in会遍历到对象的隐式属性,通常要使用 obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的
深拷贝的实现原理:
在上一文中我们已经说过,基本数据(原始数据)类型的拷贝就是值拷贝,他在效果上和深拷贝相同,手搓深浅拷贝,是涉及到对象和数组等复杂数据类型的拷贝。在掌握浅拷贝之后,在浅拷贝的基础上我们来考虑深拷贝的问题。
当遍历你需要复制的对象时,浅拷贝不会继续遍历对象中的对象的属性,只会复制引用地址;但是深拷贝需要将对象中的元素,对象中的对象中的元素...通通遍历,拿到每一个属性的值才能将他们确切的放在一个新集合里,因为复制的是每一个确切的值,所以拷贝过来的值是独立的值,原值的改变不会影响拷贝过来的值。那我们要怎么将每一个属性,每一个对象都遍历呢?对象里有对象,层层套娃怎么办?
看过我扁平化详解那篇文章的同学应该知道,运用递归的知识能很好的将对象中的嵌套一层层打开。所以我们只要先判断要复制的对象中是否有对象,如果有,就放到外面的大函数中递归解套。我们直接实操:用typeof
判断对象中的属性obj[key]
是不是对象object,同时这个属性不能为空。
完整代码如下:
js
const user = {
name: {
1: '掘',
2: '金'
},
age: 18
}
function deep() {
let newObj = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof (obj[key]) === 'object' && obj[key] !== null) {
//判断是不是对象
newObj[key] = deep(obj[key])
//递归,每次都能创建出一个新对象,防止共用引用地址
}else{
//原始类型直接赋值
newObj[key] = obj[key]
}
}
}
return newObj
}
小结:
- 借助for in 遍历原对象,将原对象的属性值增加到新对象中,
- 因为for in会遍历到对象的隐式属性,通常要使用
- 如果遍历到的属性值是原始数据类型,直接往对象中赋值,如果是引用类型,递归创建新的子对象