一道比亚迪前端面试题:深拷贝和浅拷贝有什么区别?

大家好,我是小寒。

最近有朋友参加了比亚迪的前端面试,又被问到了一道经典的前端面试题:深拷贝和浅拷贝有什么区别? 本期一起来盘一下这道题。

一、深拷贝和浅拷贝

浅拷贝的特点:

  • 只拷贝第一层
  • 对于基本数据类型,拷贝值
  • 对于引用数据类型,拷贝引用

深拷贝的特点:

  • 多层递归拷贝
  • 对于基本数据类型,拷贝值
  • 对于引用数据类型,创建一个新的引用对象,并循环拷贝到新对象中

举个例子:

const obj = {
    a: 1,
    b: [1, 2, 3],
    c: {
        d: 4,
        e: 5
    }
}

const newObj = deepClone(obj);
console.log(newObj);
/**
    打印结果:
    {
        a: 1,
        b: [1, 2, 3],
        c: {
            d: 4,
            e: 5
        }
    }
 */

对于上面的例子来说,newObj的打印结果都是相同的,不同的是,对于浅拷贝来说:

  • obj.b === newObj.b
  • obj.c === newObj.c

而对于深拷贝来说,它们前后都是不相等的。

二、浅拷贝的实现方式

2.1 展开运算符

可以用ECMAScript 2018 规范新增特性,也就是ES6提供的语法三个点(…)来实现深拷贝,具体用法如下:

const newObj = { ...obj };

2.2 Object.assign

Object.assignObject类自带的一个静态方法,可以将一个或多个对象中的可枚举(此属性的enumerable为true)自有属性(对象自身的,从原型上继承的不算)合并到目标对象中。

const newObj = Object.assign({}, obj1, obj2, ...);

这里要注意一下,展开运算符和Object.assign虽然可以实现浅拷贝,但仍有细微的区别,比如在遇到settergetter时,两者的表现不一样。来举个例子:

首先是展开运算符:

const obj1 = {
    get a() {
        console.log('getter')
        return 1;
    },
    set a(val) {
        console.log('setter')
    }
}
const obj2 = {
    a: 2,
}

const newObj = { ...obj1, ...obj2 };
console.log(newObj);
console.log(newObj.a)

console.log('------------------------');

const newObj1 = { ...obj2, ...obj1 };
console.log(newObj1);
console.log(newObj1.a)


/**
 * 打印结果:
  getter
  { a: 2 }
  2
  ------------------------
  getter
  { a: 1 }
  1
 */

从打印结果可以看出,展开运算符在拷贝时有如下特点:

  1. 合并时不会执行setter;
  2. 合并后同名getter和属性会同时存在,但取值时会按照合并的先后顺序,会先取后合并的值。

然后是Object.assign

const obj1 = {
    get a() {
        console.log('getter')
        return 1;
    },
    set a(val) {
        console.log('setter')
    }
}
const obj2 = {
    a: 2,
}

const newObj = Object.assign(obj1, obj2);
console.log(newObj);
console.log(newObj.a)

console.log('------------------------');

const newObj1 = Object.assign(obj2, obj1);
console.log(newObj1);
console.log(newObj1.a);

/**
 * 打印结果:
  setter
  { a: [Getter/Setter] }
  getter
  1
  ------------------------
  getter
  { a: 1 }
  1
 */

从打印结果可以看出,``Object.assign`在拷贝时有如下特点:

  1. 同名属性和同名gettersetter合并时,会执行setter,而同名gettersetter同名属性合并时却不会执行setter。
  2. 无论同名属性和同名gettersetter的合并先后顺序如何,最终访问只会访问到getter里面的值,只是从控制台里看的效果不一样而已。

2.3 for…in + Object.prototype.hasOwnProperty

直接用for..in循环,配合hasOwnProperty判断是否是自身的属性来进行拷贝。

function shallowClone(obj) {
    const newObj = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
        newObj[key] = obj[key];
        }
    }
    return newObj;
}

const obj = {
    a: 1,
    b: [1, 2, 3],
    c: {
        d: 4,
        e: 5
    }
};
const newObj = shallowClone(obj);
console.log(newObj)

或者用Object.keys先拿到自身的属性的数组,然后forEach循环拷贝也可,方式有很多种,大家可以自行扩展。

三、深拷贝的实现方式

3.1 JSON.parse + JSON.stringify

const newObj = JSON.parse(JSON.stringify(obj));

这种方式实现深拷贝十分简单,我在开发中也经常使用,不过这种方式实现深拷贝有以下缺点:

  1. 无法处理循环引用,遇到会报错。
  2. 不支持functionundefined,拷贝后会丢失。
  3. 无法复制某些对象类型, 遇到比如Date、RegExp、Map、Set、Error、Symbol 等 JavaScript 内置对象及其属性时,会被转换为基本的字符串或数组形式,导致前后的数据类型不一样,不符合预期。

尽管如此,它仍然在实际开发中广泛使用,比如拷贝表单数据进行前后对比等场景。

3.2 使用第三方库

比如使用loddash

const _  = require('lodash');
const newObj = _.cloneDeep(obj);
console.log(newObj);

3.3 手动实现深拷贝

const isObj = (target) => typeof target === 'object' && target !== null
function deepClone(obj, hash = new WeakMap()) {
    if (!isObj(obj)) return obj;
    if (hash.has(obj)) return has.get(obj);
    const target = new obj.constructor();
    hash.set(obj, target);
    Object.keys(obj).forEach((key) => {
        target[key] = deepClone(obj[key], hash);
    })
    return target;
}

const newObj = deepClone(obj);
console.log(newObj);

这里用WeakMap处理循环引用的问题,通过new obj.constructor直接构造对象、数组等对象的实例,然后forEach复制属性,简单实现一个深拷贝。

四、小结

本文主要分享了一道前端高频面试题,也就是深拷贝和浅拷贝的区别,希望小伙伴们读完后对浅拷贝和深拷贝有更深入的理解。

这里也分享下比亚迪的全部前端面试题:

  1. 自我介绍;
  2. 实习中的项目业务介绍;
  3. 你现在用RN开发吗?那有没有用RN做过一些手机端的适配;
  4. react和vue你觉得哪个好用?为什么?
  5. vue的生命周期;
  6. 双向绑定与响应式原理;
  7. Vue的单向数据流;
  8. Vue的组件之间的通信;
  9. 你刚说到pinia通信,那你知道pinia的原理吗?和Vuex有什么区别?
  10. 怎么使用pinia?
  11. Vue-router的所有钩子函数介绍一下;
  12. vue的单页面和多页面的区别?
  13. 防抖和节流在实习项目中用过吗?
  14. vue的computed和watch的区别?
  15. 用过Vue的脚手架吗?
  16. vite的原理知道吗?
  17. react生命周期;
  18. js中要做异步操作该怎么办?
  19. 要清除定时器该怎么操作?
  20. 深拷贝和浅拷贝有什么区别?

大家觉得这个面试题难度如何呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值