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

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

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

🏝️前言

在上一篇数组去重的文章里,使用删除元素实现数组去重时,有提到过concat()这个方法,却并没有说它的具体作用,这就和今天的浅拷贝有关了

在这之前,我们把一个变量值给另一个变量时使用的是赋值操作,赋值之后两个变量其中一个的值更改也不会影响到另一个变量

但是为什么数组赋值之后,对新数组进行去重,原来的数组也会随着新数组一块变化呢?

🏷️赋值操作

首先我们要知道赋值操作赋的是什么值

基本数据类型赋值传递的是存放在栈内的数据,而引用类型赋值传递的是他们存放在栈内的地址,它们的数据存放在这个地址指向的堆内存里

所以引用类型赋值传递的是存在栈内的地址,当对新对象进行数据的修改时,改变的是这个地址指向的堆内存里的数据

因为新旧对象使用的是相同的地址,地址指向的数据改变之后,旧对象的值也就随之改变了

但是我们一般是不想让原对象的数据改变的,那要怎么解决这个问题呢,这时候就用到了深浅拷贝

🌄浅拷贝

那先来说浅拷贝

浅拷贝创建一个新的对象,只拷贝一层,即拷贝对象里第一层基本数据类型的值和引用类型的地址

但是深层次的数据还是会互相影响

如果修改新对象里第一层基本数据类型的值,不会对旧对象产生影响,但如果修改第一层的引用类型的值,仍会对旧对象产生影响

因为新旧对象虽然不再使用同一地址,但第一层的引用类型的地址仍是相同的

举个栗子🌰

let obj_old = {
    name: 'Tom',
    age: 15,
    favorite: {
        food: 'bread',
        drink: 'milk'
    }
}
// Object.assign()是一种浅拷贝的方法
let obj_new = Object.assign({}, obj_old)
console.log(obj_old === obj_new)
console.log(obj_old.name === obj_new.name)
console.log(obj_old.favorite === obj_new.favorite)

这里的例子我们使用了浅拷贝,然后我们来对比新旧对象

obj_old === obj_new是false,这说明新旧对象的地址已经不一样了

obj_old.name === obj_new.name是true,这个是基本数据类型的值是相同的,没有问题

obj_old.favorite === obj_new.favorite也是true,这里的favorite也是对象,但是他们比较出来的结果不是false而是true,说明这个对象的地址已经是相同的了,它们共享同一块内存空间

再来对新对象里的数据进行修改

修改新对象第一层的基本数据类型时,新旧对象互不影响

obj_new.name = 'Jerry'
console.log(obj_old)
console.log(obj_new)

修改新对象第一层的引用数据类型时,也就是修改obj_new第一层的favorite对象里的属性值时,obj_old里的favorite相应的属性值也随之改变了,新旧对象相互影响

obj_new.favorite.food = 'cheese'
console.log(obj_old)
console.log(obj_new)

🏷️浅拷贝的方法

了解过浅拷贝是什么之后,这里就来罗列几个浅拷贝的方法

1. Object.assign()

语法:Object.assign(target, ...sources)

target 目标对象,接收源对象属性的对象,也是修改后的返回值。 sources 源对象,包含将被合并的属性。

代码展示

let obj_old = {
    name: 'Tom',
    age: 15,
    favorite: {
        food: 'bread',
        drink: 'milk'
    }
}
let obj_new = Object.assign({}, 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

2. 展开运算符 Spread ...

语法:{...sources}

sources 源对象,包含将被合并的属性。

代码展示

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

3. 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

4. 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)

测试结果

2. 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)

测试结果

3. 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会变成字符串

🏝️结语

这次的深拷贝浅拷贝的总结就到这里啦,后续发现不足的话会继续补充的

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

相关文章
|
4月前
|
JavaScript 前端开发
常见的JS面试题
【8月更文挑战第5天】 常见的JS面试题
61 3
|
24天前
|
JSON JavaScript 前端开发
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
本文介绍了JSONP的工作原理及其在解决跨域请求中的应用。首先解释了同源策略的概念,然后通过多个示例详细阐述了JSONP如何通过动态解释服务端返回的JavaScript脚本来实现跨域数据交互。文章还探讨了使用jQuery的`$.ajax`方法封装JSONP请求的方式,并提供了具体的代码示例。最后,通过一个更复杂的示例展示了如何处理JSON格式的响应数据。
30 2
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
|
2月前
|
消息中间件 前端开发 NoSQL
面试官最反感这样的简历!
面试官最反感这样的简历!
44 0
面试官最反感这样的简历!
|
2月前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
|
4月前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
40 0
|
4月前
|
JavaScript 前端开发 程序员
JS小白请看!一招让你的面试成功率大大提高——规范代码
JS小白请看!一招让你的面试成功率大大提高——规范代码
|
4月前
|
SQL 存储 关系型数据库
|
4月前
|
JavaScript 前端开发 UED
小白请看! 大厂面试题 :如何用JS实现瀑布流
小白请看! 大厂面试题 :如何用JS实现瀑布流
|
4月前
|
存储 JavaScript 前端开发
JS浅拷贝及面试时手写源码
JS浅拷贝及面试时手写源码
|
4月前
|
JavaScript 前端开发
JS:类型转换(四)从底层逻辑让你搞懂经典面试问题 [ ] == ![ ] ?
JS:类型转换(四)从底层逻辑让你搞懂经典面试问题 [ ] == ![ ] ?