三分钟手搓深浅拷贝实现原理

前言

手搓深浅拷贝是面试官的常考题,在上一篇的学习中我们深度学习了拷贝,熟悉运用了深浅拷贝的方法。在本文中我们将利用拷贝的原理手搓一个函数来实现拷贝的功能。

浅拷贝的实现原理

如何实现浅拷贝?

首先我们要知道通俗来讲拷贝就是将一个对象中的属性全部复制到另一个集合里面。全部复制--那就需要遍历。在数组中,我们可以轻松的利用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
}

小结一下

  1. 借助for in 遍历原对象,将原对象的属性值增加到新对象中,
  2. 因为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
}

小结:

  1. 借助for in 遍历原对象,将原对象的属性值增加到新对象中,
  2. 因为for in会遍历到对象的隐式属性,通常要使用
  3. 如果遍历到的属性值是原始数据类型,直接往对象中赋值,如果是引用类型,递归创建新的子对象
相关推荐
余生H几秒前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍3 分钟前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai8 分钟前
网站开发的发展(后端路由/前后端分离/前端路由)
前端
流烟默19 分钟前
Vue中watch监听属性的一些应用总结
前端·javascript·vue.js·watch
2401_8572979130 分钟前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
茶卡盐佑星_40 分钟前
meta标签作用/SEO优化
前端·javascript·html
与衫1 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql
Ink1 小时前
从底层看 path.resolve 实现
前端·node.js
金灰1 小时前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5
茶卡盐佑星_1 小时前
说说你对es6中promise的理解?
前端·ecmascript·es6