深浅拷贝之谜:解开数据复制的神秘面纱
wxin55 2024-11-17 02:44 8 浏览 0 评论
前言
拷贝在前端中非常常见,也是面试中常考的主题,今天我们聊聊前端中的深浅拷贝问题,这里我们只针对引用类型的拷贝,基本数据我们类型不聊,因为所有的基本数据类型的拷贝都是深拷贝。
本质
我们知道调用栈里面变量的存储方式使用键值对的方式存储,key是变量名,value就是值。基本数据类型的变量和值都存放于栈中,而引用类型变量存放于栈中,但是其真实值保存的堆中,栈里面的值存放的其实是堆地址,也就是其真实值的地址。当我们拷贝一个基本数据类型时,在栈里面就是新创建了一个变量,并将值赋给这个变量,当我们修改原对象时,不会影响到拷贝的变量,而这个过程也被称之为深拷贝。但是对于引用类型,当我们创建一个新变量时,是将保存的地址赋给这个新变量,也就是说新拷贝的对象和旧对象其实是引用的同一片地址,我们v8在查找引用类型时,其实是顺着栈里面存放的地址跑的去堆里面查找的,当我们修改原对象的值的时候,也相当于修改了新拷贝的对象的值,通俗的来讲就是两个指针指向了同一片地址,任何一方修改都会影响另一方,这就是浅拷贝。
浅拷贝
通俗的来讲就是基于原对象,拷贝得到一个新的对象,原对象中内容的修改会影响新对象。以下是常见的浅拷贝方法
1. Object.create()
这个方法是用来创建一个新对象的,上次我们聊到用这个方法创建的对象是没有原型的。当我们修改原对象的属性时,新拷贝的对象也会受影响。
let obj = {
age :18
}
let newObj= Object.create(obj)
obj.age=20;
console.log(newObj.age);//20
2. Object.assign({},a)
这个方法是用于将两个对象的属性进行拼接,返回一个新对象。这里我们用一个空对象和一个对象拼接,这样也算是一种拷贝,可以发现当我修改基本数据类型时,新拷贝的对象不受影响,但是修改引用类型里面的值时,新拷贝的对象会被影响。
let a = {
name :'wzm',
like : {
sport:'run'
}
}
let c = Object.assign({},a);
a.name = 'xxj'
a.like.sport = 'swim'
console.log(c);//{ name: 'wzm', like: { sport: 'swim' } }
3. [].concat(arr)
这个方法是将两个数组拼接,并返回一个新数组。这里我们用一个空数组和一个数组拼接,可以发现当我们修改原数组的基本数据类型时,新数组不受影响,但是修改引用类型里面的值时,新拷贝的对象会被影响。
let arr = [1,2,3,{a:10}]
let newArr = [].concat(arr)
arr[2] = 4;
arr[3].a = 100;
console.log(newArr);//[ 1, 2, 3, { a: 100 } ]
4. 数组解构 ...
数组解构是es6新增的语法,...表示将arr中的所有元素放到newArr中,当然还有其他的一些解构的语法,这里就不做过多赘述。
let arr = [1,2,3,{a:10}]
let newArr = [...arr]
arr[2] = 4;
arr[3].a = 100;
console.log(newArr);//[ 1, 2, 3, { a: 100 } ]
5. arr.slince(0)
数组身上的一个方法,他和splice很像,splice是新增或者删除数组中的一个元素,slince可从已有的数组中返回选定的元素,当我们填0时,将返回整个数组。
let arr = [1,2,3,{a:10}]
let newArr = arr.slice(0)
arr[2] = 4;
arr[3].a = 100;
console.log(newArr);//[ 1, 2, 3, { a: 100 } ]
6. arr.toReversed().reverse()
toReversed返回一个元素顺序相反的新数组,原始数组保持不变,reverse原地修改原始数组,将数组中的元素顺序相反。
let arr = [1,2,3,{a:10}]
let newArr = arr.toReversed().reverse();
arr[2] = 4;
arr[3].a = 100;
console.log(newArr);//[ 1, 2, 3, { a: 100 } ]
以上是一些JavaScript中常见的浅拷贝方法,接下来我们手搓一个浅拷贝出来。
创建新对象,for in 遍历传入的对象,使用obj.hasOwnProperty(key)将对象身上的隐式属性去除,将对象身上的显示属性添加到新对象中,返回这个新对象
let obj = {
name :'wzm',
like :{
sport:'run'
}
}
function shallow(obj){
let newObj = {}
// for in 不仅会遍历到显示具有的属性,还有遍历到构造函数上的显示属性
for(let key in obj){
// 去除掉对象身上隐式原型的属性
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key];
}
}
return newObj;
}
let newObj = shallow(obj)
obj.like.sport = 'swim'
console.log(newObj);//{ name: 'wzm', like: { sport: 'swim' } }
深拷贝
通俗的来讲就是基于原对象,拷贝得到一个新的对象,原对象中内容的修改不会影响新对象,以下是常见的深拷贝方法
1. JSON.parse(JSON.stringify(obj))
JSON.stringify 将对象转换为字符串 JSON.parse 将字符串转换为对象,这种拷贝方式有几个地方需要注意,它不能识别BigInt类型,它不能拷贝undefined,symbol,function类型的值,它不能处理循环引用
let obj = {
name: '萍萍',
age: 18,
like: {
n: 'coding'
},
a: true,
b: undefined,
c: null,
d: Symbol(1),
f: function() {}
}
// JSON.stringify 将对象转换为字符串 JSON.parse 将字符串转换为对象
let obj2 = JSON.parse(JSON.stringify(obj));//{ name: '萍萍', age: 18, like: { n: 'coding' }, a: true, c: null }
2. structuredClone()
这是JavaScript官方专门打造出来的用于深拷贝创建对像的方法,比较新。
const user = {
name:{
firstName:'w',
lastName:'m'
},
age:19
}
const newUser = structuredClone(user)
user.name.firstName = 'x'
user.age = 20
console.log(newUser);//{ name: { firstName: 'w', lastName: 'm' }, age: 19 }
以上就是比较常见的深拷贝方法,老样子我们手写一个深拷贝方法出来,和浅拷贝十分类似,但是略有不同。我们还是要创建一个空对象,for in 遍历传入的对象,深拷贝不能直接将传入的对象的属性直接赋值给新对象,而是需要判断,如果遍历到对象身上的显示属性是基本数据类型时,将对象身上的显示属性添加到新对象中,如果是引用类型时,则递归创建新的子对象,这样就避免了引用同一片地址。
const user = {
name:{
firstName:'w',
lastName:'m'
},
age:19
}
function deep(obj){
let newObj = {}
for(let key in obj){
if(obj.hasOwnProperty(key)){
//另一种判断是不是Object对象的方法 typeof(obj[key])=='object'&&obj[key]!=null
if(obj[key] instanceof Object){
newObj[key] = deep(obj[key]);
}else{
newObj[key] = obj[key]
}
}
}
return newObj;
}
let newUser = deep(user)
user.name.firstName = 'x'
console.log(newUser);
总结
今天我们学习了什么是深拷贝和浅拷贝,区别就是拷贝的新对象会不会被原对象的修改而受影响,其本质还是在于存储方式的不同。我们还学习了JavaScript中常见的几种浅拷贝方法,有Object.create(obj) , Object.assign({},obj) , [].concat(arr) , arr.slince(0) , 数组解构 [...arr] ,arr.toReversed().reverse() 等,常见的深拷贝方法有,JSON.parse(JSON.stringify(obj)) , structuredClone() ,我们还手搓了两种拷贝的方法,注意 for in 遍历对象的弊端,他会遍历到原型上的属性,我们需要用hasOwnProperty方法去除。OK今天我们就聊到这里欢迎下次再见。
链接:https://juejin.cn/post/7371386534178930715
相关推荐
- ES6中 Promise的使用场景?(es6promise用法例子)
-
一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...
- JavaScript 对 Promise 并发的处理方法
-
Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...
- Promise的九大方法(promise的实例方法)
-
1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...
- 360前端一面~面试题解析(360前端开发面试题)
-
1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...
- 前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?
-
Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...
- 最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式
-
看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...
- 前端分享-Promise可以中途取消啦(promise可以取消吗)
-
传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...
- 手写 Promise(手写输入法 中文)
-
前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...
- 什么是 Promise.allSettled()!新手老手都要会?
-
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...
- 前端面试-关于Promise解析与高频面试题示范
-
Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...
- 宇宙厂:为什么前端离不开 Promise.withResolvers() ?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...
- Promise 新增了一个超实用的 API!
-
在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...
- 一次搞懂 Promise 异步处理(promise 异步顺序执行)
-
PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...
- Promise 核心机制详解(promise机制的实现原理)
-
一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...
- javascript——Promise(js实现promise)
-
1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- ES6中 Promise的使用场景?(es6promise用法例子)
- JavaScript 对 Promise 并发的处理方法
- Promise的九大方法(promise的实例方法)
- 360前端一面~面试题解析(360前端开发面试题)
- 前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?
- 最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式
- 前端分享-Promise可以中途取消啦(promise可以取消吗)
- 手写 Promise(手写输入法 中文)
- 什么是 Promise.allSettled()!新手老手都要会?
- 前端面试-关于Promise解析与高频面试题示范
- 标签列表
-
- hive行转列函数 (63)
- sourcemap文件是什么 (54)
- display none 隐藏后怎么显示 (56)
- 共享锁和排他锁的区别 (51)
- httpservletrequest 获取参数 (64)
- jstl包 (64)
- qsharedmemory (50)
- watch computed (53)
- java中switch (68)
- date.now (55)
- git-bash (56)
- 盒子垂直居中 (68)
- npm是什么命令 (62)
- python中+=代表什么 (70)
- fsimage (51)
- nginx break (61)
- mysql分区表的优缺点 (53)
- centos7切换到图形界面 (55)
- 前端深拷贝 (62)
- kmp模式匹配算法 (57)
- jsjson字符串转json对象 (53)
- jdbc connection (61)
- javascript字符串转换为数字 (54)
- mybatis 使用 (73)
- 安装mysql数据库 (55)