原生js深刻理解系列(四)--- 多个实例深刻理解js的深拷贝和浅拷贝,多种方法实现对象的深拷贝

亲们为何要研究深拷贝和浅拷贝呢,由于咱们项目开发中有许多状况须要拷贝一个数组抑或是对象,可是单纯的靠=“赋值”并不会解决全部问题,若是遇到引用类型的对象改变新赋值的对象会形成原始对象也发生一样改变,而要去除影响就必须用到浅拷贝、深拷贝,深拷贝,对于引用对象须要进行深拷贝才会去除影响。若是是值类型直接“=”就好。es6

简而言之:数组

赋值:就是两个对象指向的内存地址同样,a=b赋值后的新对象也指向同一个存储地址因此b变化a跟随变化,函数

浅拷贝:拷贝对象的一级元素的地址,若是一级元素所有为值类型就会互不干扰,若是一级元素有引用类型,改变引用类型的里面的值,会改变原对象。学习

深拷贝:拷贝对象各级元素的存储地址。prototype

1.引用类型  引用类型一般叫作类(class),也就是说,遇到引用值,所处理的就是对象。new 出来的对象都是引用对象3d

(1)值类型:String, 数值、布尔值、null、undefined。 对于值类型一个对象一个存储位置因此会互不干扰指针

(2)引用类型:对象、数组、函数。对于引用类型,a=b赋值后的新对象也指向同一个存储地址因此b变化a跟随变化,code

下图若是a是{key:'1', value:'value',children:{key:'child'}}这样一个对象,a赋值给b的话,a,b的存储位相同,里面的值变化也相同。若是a浅拷贝给b,a的值类型的值会复制给b,而a和b指向的引用类型的存储位置会相同,a引用类型里的children.key的值变化会引发b的children.key的值变化。即指针指向位置相同,那么该位置里的值也相同对象

而要改变这种状况须要改变b的指向,使其指向b存储位置以下图blog

下面是各类实例对比:

第一模块-数组:

数组的赋值:

a =[1,2,3];
b=a;
b.push('change');
console.log('a:'+a,'b:'+b)
// 结果 VM529:1 a:1,2,3,change    b:1,2,3,change, 数组元素值的变化会互相影响

数组浅拷贝:// 浅拷贝,拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用

还有浅拷贝数组的话能够利用,splice() 和 slice() 这两个方法。他们的区别一个是splice能够改变数组自己,slice不能改变数组自己。

Array.from() 方法从一个相似数组或可迭代对象中建立一个新的,浅拷贝的数组实例。

数组深拷贝:// 深拷贝,遍历到到每一项都是值类型时能够直接赋值具体实现参考本文下面对象深拷贝。下面代码代码是简单的遍历数组没有进行处理数组里面嵌套数组和对象的状况。

a =[1,2,3];
let b =[]
a.forEach(val => b.push(val));b.push('change');
console.log('a:'+a,'b:'+b)
// 结果VM167:5 a:1,2,3      b:1,2,3,change

第二模块-对象:

1-1对象赋值的代码及结果

bb改变后原数组aa也跟随变化,根本缘由就是像第一张线框图描述的同样

1-2对象对象浅拷贝的代码及结果代码及结果,使用es6的Object.assign()方法

浅拷贝bb不会影响aa,由于改变是是值类型,可是若是是改变引用类型的值呢?以下图

结果很显然,对于引用对象Object.assign()就不行了,有点鸡肋了。aa的children里面的key值随bb的改变而改变

1-3对象深拷贝使用JSON.parse(JSON.stringify(obj)),即对象序列化

给子对象children改变属性以下图所示,aa原对象是不变的。可是JSON.parse(JSON.stringify(obj))实现深拷贝会存在一些问题,好比序列化会将序列化的undefined丢失,序列化正则对象(RegExp)会返回{},obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null,对于序列化构造函数construct()会被丢失
 

1-4对象深拷贝使用es6的Object.keys(obj),Object.values(obj)分别得到键数组和值数组,再经过函数根据条件循环回调deal()获得深拷贝值:《推荐使用回调》,留下个小问题,看官们能够试着这个思路去完成数组和函数回调实现深拷贝。

可复制代码:

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};

function deal(obj, bb = {}) {
    let keyArr = Object.keys(obj);
    let valueArr = Object.values(obj);
    valueArr.forEach((val,index) => {
    console.log(Object.prototype.toString.call(val))
        if(Object.prototype.toString.call(val) === "[object Object]") {
             bb[keyArr[index]] = deal(val, bb[keyArr[index]])
        }else {
            bb[keyArr[index]] = val
        }
    })
    return bb
}
BB = {}
BB=deal(aa);
console.log(BB)
BB['add']='addStr';
BB['children']['change']='变'
console.log('aa:',aa,'========','BB:',BB)

截图代码和结果:

第三模块对于对象来讲可复制的浅、深拷贝代码和结果事例以下:

实例一

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb=aa;
bb['add']='addStr';
console.log('aa:',aa,'====','bb',bb) 
// 下面结果出现aa,bb都有add属性,使用等号没法实现对象的拷贝,这个一样适用于数组

VM1098:9 aa: {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object ==== bb {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object

实例二

aa = {
   key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb = {}
bb=Object.assign({},aa);
bb['add']='addStr';
console.log('aa:',aa,'====','bb',bb)
// 下面结果出现aa没有add属性,bb有add属性,使用assign能够实现对象的第一级键值对的拷贝。

VM1223:10 aa: {key: "1", value: "a", children: {…}}children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object ==== bb {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectconstructor: ƒ Object()hasOwnProperty: ƒ hasOwnProperty()isPrototypeOf: ƒ isPrototypeOf()propertyIsEnumerable: ƒ propertyIsEnumerable()toLocaleString: ƒ toLocaleString()toString: ƒ toString()valueOf: ƒ valueOf()defineGetter: ƒ defineGetter()defineSetter: ƒ defineSetter()lookupGetter: ƒ lookupGetter()lookupSetter: ƒ lookupSetter()get proto: ƒ proto()set proto: ƒ proto()key: "1"value: "a"proto: Object

对于改变引用对象源码

实例以下,浅拷贝改变引用对象里面的值事,原对象也会改变

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb = {}
bb=Object.assign({},aa);
bb['children']['key']='addStr';
console.log('aa:',aa,'====','bb',bb) // ,结果以下:
// 下面结果出现aa,bb的引用属性children里面的key属性都变成了addStr,使用assign不能够实现对象里引用属性的拷贝,所以assign()为浅拷贝。由于 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。

在这里插入图片描述

实例三

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb = {}
bb=JSON.parse(JSON.stringify(aa));
bb['add']='addStr';
bb.children.key='key';
console.log('aa:',aa,'====','bb',bb)
// 下面结果出现aa保持初始状态,bb变成修改后的状态,使用JSON.parse(JSON.stringify(aa))为深拷贝。

欢迎转载,转载请注明出处。欢迎你们交流学习

相关文章
相关标签/搜索