【面试题】马上金九银十了,简历该准备起来了,面试题你准备好了吗 ?浅谈 JS 浅拷贝和深拷贝

代码展示

let obj_old = {
    name: 'Tom',
    age: 15,
    favorite: {
        food: 'bread',
        drink: 'milk'
    }
}
let obj_new = {...obj_old}
console.log(obj_old === obj_new)  // false
console.log(obj_old.name === obj_new.name)  // true
console.log(obj_old.favorite === obj_new.favorite)  // true
  1. Array.prototype.concat()

语法:arr.concat(value0, /* ... ,*/ valueN)

注:如果省略了所有 valueN 参数,则 concat 会返回调用此方法的现存数组的一个浅拷贝。

代码展示

let arr_old = [1, 2, {name: 'Tom'}]
let arr_new = arr_old.concat()
console.log(arr_old === arr_new)  // false
console.log(arr_old.name === arr_new.name)  // true
  1. Array.prototype.slice()

语法:arr.slice(begin, end)

注:如果省略了 begin, end 参数,则 slice 会返回调用此方法的现存数组的一个浅拷贝。

代码展示

let arr_old = [1, 2, {name: 'Tom'}]
let arr_new = arr_old.slice()
console.log(arr_old === arr_new)  // false
console.log(arr_old.name === arr_new.name)  // true

🌄深拷贝

接下来说深拷贝

深拷贝就是在堆内存中开辟一个新的空间存放新对象,拷贝原对象的所有属性,拷贝前后两个对象互不影响

深拷贝的新旧对象不共享内存

这时我们去修改新对象中的任意层级的任意属性值,都不会对原对象产生影响,原对象依然保持不变

举个栗子🌰

let obj_old = {
  name: 'Tom',
  age: 15,
  hobby: ['eat', 'game'],
  favorite: {
      food: 'bread',
      drink: {
        dname: 'milk',
        color: 'white',
      },
  }
}
let obj_new = _.cloneDeep(obj_old)
console.log(obj_old)
console.log(obj_new)
console.log(obj_old.name === obj_new.name)
console.log(obj_old.favorite === obj_new.favorite)
console.log(obj_old.favorite.drink === obj_new.favorite.drink)

这里我们使用了lodash工具库提供的_.cloneDeep深拷贝方法,来看一下新旧对象的对比

可以看见新旧对象所有属性及属性值完全相同

那再来细节对比一下,看看拷贝后对象的地址是否相同

obj_old.name === obj_new.name为true,这个是基本数据类型,完全相同,没有问题
obj_old.favorite === obj_new.favorite,这里为false,到这里就和浅拷贝不同了,浅拷贝到这一层的时候,对象属性的地址是相同的,而深拷贝是完全拷贝出一个新的对象,所以不管是哪一层,对象属性的地址都是不同的
obj_old.favorite.drink === obj_new.favorite.drink为false,再往深一层仍然是false,即地址不相同

那再来修改一下属性值,看看前后对比

obj_new.name = 'Jerry'
obj_new.hobby[0] = 'sing'
obj_new.favorite.food = 'cheese'

修改属性值之后,新旧对象是互不影响的

🏷️深拷贝的方法

那么现在就来罗列几个深拷贝的方法

  1. 递归实现

使用递归实现原对象每一层的拷贝,当遇到基本数据类型时,直接拷贝,遇到引用数据类型时,递归拷贝它的每个属性

代码展示

const obj_old = {
  name: 'Tom',
  age: 15,
  hobby: ['eat', 'game'],
  favorite: {
      food: 'bread',
      drink: {
        dname: 'milk',
        color: 'white',
      },
  }
}
const obj_new = {}

function deepClone(newObj, oldObj){
  for (const key in oldObj) {
    if (oldObj[key] instanceof Object) {
      newObj[key] = {}
      deepClone(newObj[key], oldObj[key])
    } else if (oldObj[key] instanceof Array) {
      newObj[key] = []
      deepClone(newObj[key], oldObj[key])
    } else {
      newObj[key] = oldObj[key]
    }
  }
}

deepClone(obj_new, obj_old)

console.log(obj_old)
console.log(obj_new)
console.log(obj_old.name === obj_new.name)
console.log(obj_old.favorite === obj_new.favorite)
console.log(obj_old.favorite.drink === obj_new.favorite.drink)

测试结果

  1. lodash工具包

点这里去lodash的中文文档

引入成功后,我们直接使用lodash提供给我们的函数_.cloneDeep就行

代码展示

let obj_old = {
  name: 'Tom',
  age: 15,
  hobby: ['eat', 'game'],
  favorite: {
      food: 'bread',
      drink: {
        dname: 'milk',
        color: 'white',
      },
  }
}
let obj_new = _.cloneDeep(obj_old)

console.log(obj_old)
console.log(obj_new)
console.log(obj_old.name === obj_new.name)
console.log(obj_old.favorite === obj_new.favorite)
console.log(obj_old.favorite.drink === obj_new.favorite.drink)

测试结果

  1. JSON.parse(JSON.stringify(obj))

JSON.stringify() 将JSON格式的对象转为字符串

JSON.parse() 将JSON格式的字符串转为对象

代码展示

const obj_old = {
  name: 'Tom',
  age: 15,
  hobby: ['eat', 'game'],
  favorite: {
      food: 'bread',
      drink: {
        dname: 'milk',
        color: 'white',
      },
  }
}
const obj_new = JSON.parse(JSON.stringify(obj_old))

console.log(obj_old)
console.log(obj_new)
console.log(obj_old.name === obj_new.name)
console.log(obj_old.favorite === obj_new.favorite)
console.log(obj_old.favorite.drink === obj_new.favorite.drink)

测试结果

❣️虽然这个方法最简单,代码行数最少,但是它也有一定的缺陷:

  1. 拷贝对象的值中如果有'函数','undefined','symbol' JSON.stringify()序列化后,键值对丢失
  2. 拷贝RegExp会变成空对象{}
  3. 对象中含有'NaN','Infinity'会变成null
  4. 拷贝Date会变成字符串

🏝️结语

其实前端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

这里再分享一个复习的路线:(以下体系的复习资料是我从各路大佬收集整理好的)

《前端开发四大模块核心知识笔记》

最后,说个题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。

相关推荐
Half-up2 分钟前
C语言心型代码解析
c语言·开发语言
别拿曾经看以后~9 分钟前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死12 分钟前
导航栏及下拉菜单的实现
前端·css·css3
川石课堂软件测试15 分钟前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
科技探秘人24 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
Source.Liu24 分钟前
【用Rust写CAD】第二章 第四节 函数
开发语言·rust
monkey_meng24 分钟前
【Rust中的迭代器】
开发语言·后端·rust
科技探秘人24 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
余衫马27 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
JerryXZR30 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6