js对象赋值只保留存在的属性_吐血整理 js 深浅拷贝问题

最近碰到一个问题,比如我在搜索条件参数是如下所示

searchParams

但是呢,我在向后台发送请求的时候,我需要带一个 id,以及这个 searchParams,但是又不能传递多个参数,只能传递一个 object 对象过去,又不影响原有的 searchParams,所以想到的方式只有深拷贝

一直使用的是 JSON.parse(), JSON.stringify() 但是好像之前看过一篇文章是讲述会破坏什么继承链之类的,性能不太好,所以今天特地来研究下深浅拷贝这个问题

  • 基础认识---基本类型
  • 基础认识---引用类型
  • 浅拷贝的实现-对象&&数组
  • 深拷贝的实现-对象&&数组
  • 深拷贝的实现- ES6扩展运算符实现对象&&数组的深拷贝
  • 深拷贝的实现-递归的方法
  • 深拷贝的实现-JSON.stringify/parse的方法

基础认识:

对于js的对象的深拷贝和浅拷贝,必须先提到的是JavaScript的数据类型, 我们先来理解一些js基本的概念 —— Javascript有五种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boolean,Number和String,并且基本类型存放在栈内存。还含有一种复杂的数据类型(也叫引用类型)存放在堆内存,就是对象(Object,Array)。 堆内存用于存放由new创建的对象,栈内存存放一些基本的类型的变量和对象的引用变量。
注意Undefined和Null的区别,Undefined类型只有一个值,就是undefined,Null类型也只有一个值,也就是null
Undefined 其实就是已声明未赋值的变量输出的结果
null 其实就是一个不存在的对象的结果。
JS 中的浅拷贝与深拷贝,只是针对复杂数据类型(Object,Array)的复制问题。浅拷贝与深拷贝都可以实现在已有对象上再生出一份的作用。但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此拷贝的时候就存在两种情况了:拷贝引用和拷贝实例,这也是浅拷贝和深拷贝的区别

1. 对于基本数据类型

他们的值在内存中占据着固定大小的空间,并被保存在栈内存中。当一个变量向另一个变量复制基本类型的值,会创建这个值的副本,并且我们不能给基本数据类型的值添加属性

var 

668c0966bf3ddbb476097724d6c134cb.png

上面的代码中,a是基本数据类型(Number), b是a的一个副本,它们两者都占有不同位置但相等的内存空间,只是它们的值相等,若改变其中一方,另一方不会随之改变

2. 对于引用类型

复杂的数据类型即是引用类型,它的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针地址而已,因此两个变量最终都指向同一个对象

var 

d812a002c0b99fdf479c2dec568960a1.png

709a7f66df5f730506ca383612297bad.png

注:0x123指针地址
我们可以看到obj赋值给obj2后,但我们改变其中一个对象的属性值,两个对象都发生了改变,根本原因就是obj和obj2两个变量都指向同一个指针,赋值时只是复制了指针地址,它们指向同一个引用,所以当我们改变其中一个的值就会影响到另一个变量的值。


一、深拷贝和浅拷贝的区别

浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;

深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变

浅拷贝的实现


浅拷贝的意思就是只复制引用,而未复制真正的值,有时候我们只是想备份数组,但是只是简单让它赋给一个变量,改变其中一个,另外一个就紧跟着改变,但很多时候这不是我们想要的。
(1)对象的浅拷贝: 上面的代码就是对象的浅拷贝的例子
(2)数组的浅拷贝

var 

619bd394444a2115bb1a6c58e71f9070.png

上面的代码是最简单的利用 = 赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 arr2 和 arr 改变,arr 和 arr2 也随着发生了变化

深拷贝的实现

(1)数组的深拷贝 对于数组我们可以使用slice() 和 concat() 方法来解决上面的问题

注意:(slice() 和 concat()对数组的深拷贝是有局限性的。

**slice **

var 

c21c351eb09476ca2e38dc2e453e7b3d.png

concat

var 

f4e2d145c171df78a8dde301b0c16aa1.png

针对上面说的slice() 和 concat()对局限性,我们可以继续看下面的例子:

var 

33f8c8bdd81b9b2fb65c62e8c67ce8ce.png

可以发现使用.concat()和浅复制的结果一样,这是为什么呢,那slice()会出现同样的结果吗?继续看写看例子

var 

19d3d0b72f1427cb536f586442ea66e0.png
var 

94d19748fed342202a5426834c15f70a.png

哟,也是出现同样的结果呀,原来由于上面数组的内部属性值是引用对象(Object,Array),slice和concat对对象数组的拷贝,整个拷贝还是浅拷贝,拷贝之后数组各个值的指针还是指向相同的存储地址.因此,slice和concat这两个方法,仅适用于对不包含引用对象的一维数组的深拷贝
注(补充点):

  • arrayObj.slice(start, [end]) 该方法返回一个 Array 对象,其中包含了 arrayObj 的指定部分。不会改变原数组
  • arrayObj.concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。 其实也就是下面实现的方式,但还是用上面的方法来实现比较简单高效些
function 

ES6扩展运算符实现数组的深拷贝

var 

8aa47cd4bd63344c408759a29aff277d.png

(2)对象的深拷贝 对象的深拷贝实现原理: 定义一个新的对象,遍历源对象的属性 并 赋给新对象的属性 主要是两种:

  • 利用递归来实现每一层都重新创建对象并赋值
  • 利用 JSON 对象中的 parse 和 stringify
  • ES6扩展运算符实现对象的深拷贝
var 

6f4eae53b80fc4c0bb966db00956f053.png
var 

e4feed3fe6a941f066db4c25bd75cb5f.png

obj2是在堆中开辟的一个新内存块,将obj1的属性赋值给obj2时,obj2是同直接访问对应的内存地址。

递归的方法

递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,简单粗暴上代码:

function 

那我们来试试个例子:

93edff70d6c500eef956517339363877.png

对象与Json相互转换
我们先看这两种方法:SON.stringify/parse的方法
The JSON.stringify() method converts a JavaScript value to a JSON string.
**JSON.stringify **是将一个 JavaScript 值转成一个 JSON 字符串。
The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string.
**JSON.parse **是将一个 JSON 字符串转成一个 JavaScript 值或对象。
JavaScript 值和 JSON 字符串的相互转换。
来一步步看下面的封装层例子:

function  

未封装和封装的进行比较:

const 

虽然上面的深拷贝很方便(请使用封装函数进行项目开发以便于维护),但是,只适合一些简单的情景(Number, String, Boolean, Array, Object),扁平对象,那些能够被 json 直接表示的数据结构。function对象,RegExp对象是无法通过这种方式深拷贝。

注意

var  

举例:

const 

发现在 cloneObj 中,有属性丢失了。。。是什么原因? 在 MDN 上找到了原因:

If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
undefined、function、symbol 会在转换过程中被忽略。。。 明白了吧,就是说如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝

深入JavaScript基础之深浅拷贝 - 掘金​juejin.im
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值