js中的深浅拷贝
文章类型:Javascript
发布者:admin
发布时间:2023-02-28
一:类型
javascript变量包含两种不同数据类型的值:基本类型和引用类型
1:基本类型String、Number、Boolean、Null、Undefined、Symbol
2:引用类型Object(Object、Array、Function)
二:存储方式
1:栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
2:堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值
三:传递方式
1:基本类型采取值传递方式
2:引用类型采取地址传递方式
四:定义
1:浅拷贝:复制对象的时候,只对第一层进行复制,深层对象还是指向原有地址
2:深拷贝:复制整个对象,以及深层次的嵌套对象,两者相互分离,互不影响
六:方式
1:浅拷贝
原理:创建一个新的对象,来接受你要重新复制或引用的对象值
实现:
a:object.assign 多个js对象的合并,只能拷贝浅层
let target = {};
let source = { a: { b: 1 } };
Object.assign(target, source);
console.log(target); // { a: { b: 1 } };
b:扩展运算方式,在构造对象的同时完成浅拷贝的功能
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
c:concat拷贝数组 连接一个含有引用类型的数组时,需要注意修改原数组中的元素的属性,因为它会影响拷贝之后连接的数组
let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr); // [ 1, 2, 3 ]
console.log(newArr); // [ 1, 100, 3 ]
d:slice 拷贝数组,返回一个新的数组对象
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]
e:手工实现,主要是对基本类型拷贝,对引用类型开辟新的存储,拷贝一层对象数据
function myShallow(target){
if(target!=null && typeof target=='object'){
const newData=Array.isArray(target) ? [] :{}
for(let item in target){
if(target.hasOwnProperty(item)){
newData[item]=target[item]
}
}
return newData
}else{
return target
}
}
let obj={
a:1,
b:{
name:'测试',
c:{
age:10
}
}
}
let newObj=myShallow(obj)
console.log(newObj)
2:深拷贝
原理:将一个对象完整拷贝一份给目标对象,并且从堆内存开辟全新的空间存放
1:JSON.stringify,把对象序列化成字符串,然后再转成新的对象,
注意:该方法有局限性,慎用,像函数、undefined、symbol等
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
2:手写方式1 通过递归进行 for in 遍历,不能复制不可枚举的属性以及 Symbol 类型针对普通的引用类型的值做递归复制,
对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
对象的属性里面成环,即循环引用没有解决
function myShallow(target){
if(target!=null && typeof target=='object'){
const newData=Array.isArray(target) ? [] :{}
for(let item in target){
if(target[item]!=null && typeof target[item]=='object'){
newData[item]=myShallow(target[item])
}else{
if(target.hasOwnProperty(item)){
newData[item]=target[item]
}
}
}
return newData
}else{
return target
}
}
let obj={
a:1,
b:{
name:'测试',
c:{
age:10
}
}
}
let newObj=myShallow(obj)
console.log(newObj)
改进版本,进行Date、RegExp、Error加入验证
function myShallow(target,item){
const newData=Array.isArray(target) ? [] :{}
if (Object.prototype.toString.call(target) === '[object Date]' ){
newData[item]=new Date(target)
}
if (Object.prototype.toString.call(target) === '[object RegExp]' ){
newData[item]=new RegExp(target)
}
if (Object.prototype.toString.call(target) === '[object Error]' ){
newData[item]=new Error(target)
}
if(target!=null && typeof target=='object'){
for(let item in target){
if(target[item]!=null && typeof target[item]=='object'){
newData[item]=myShallow(target[item],item)
}else{
if(target.hasOwnProperty(item)){
newData[item]=target[item]
}
}
}
return newData
}else{
return target
}
}
let obj={
a:1,
b:{
date: new Date(),
reg: new RegExp('/我是一个正则/ig'),
name:'测试',
c:{
age:10
}
}
}
let newObj=myShallow(obj)
console.log(newObj)
改进2 增加函数方式判断 ,主要根据普通函数和箭头函数, 区分这两者只需要看有无prototype, 有prototype属性就属于普通函数, 没有就是箭头函数
function myShallow(target,item){
const newData=Array.isArray(target) ? [] :{}
if(typeof target==='function'){
newData[item]=target[item].prototype? eval(`(${target[item].toString()})`) : eval(target[item].toString())
}
if (Object.prototype.toString.call(target) === '[object Date]' ){
newData[item]=new Date(target)
}
if (Object.prototype.toString.call(target) === '[object RegExp]' ){
newData[item]=new RegExp(target)
}
if (Object.prototype.toString.call(target) === '[object Error]' ){
newData[item]=new Error(target)
}
if(target!=null && typeof target=='object'){
for(let item in target){
if(target[item]!=null && typeof target[item]=='object'){
newData[item]=myShallow(target[item],item)
}else{
if(target.hasOwnProperty(item)){
newData[item]=target[item]
}
}
}
return newData
}else{
return target
}
}
let obj={
a:1,
b:{
date: new Date(),
reg: new RegExp('/我是一个正则/ig'),
func: function () { console.log('我是一个函数') },
name:'测试',
c:{
age:10
}
}
}
let newObj=myShallow(obj)
console.log('原值')
console.log(obj)
console.log('新值')
console.log(newObj)
改进3
3:手写方式2,进行递归实现,改进上面写法的不足针对Symbol、Date、RegExp,
针对Symbol使用Reflect.ownKeys
针对Date、RegExp 类型,则直接生成一个新的实例返回
利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object.create 方法创建一个新对象,并继承传入原对象的原型链;
利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date) {
return new Date(obj) // 日期对象直接返回一个新的日期对象
}
if (obj.constructor === RegExp){
return new RegExp(obj) //正则对象直接返回一个新的正则对象
}
//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) {
return hash.get(obj)
}
let allDesc = Object.getOwnPropertyDescriptors(obj)
//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
// 把cloneObj原型复制到obj上
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}