JavaScript深拷贝、浅拷贝和负值区别

深、浅拷贝和赋值

首先说明一下,深拷贝、浅拷贝和等号负值的区别是建立在对引用数据类型(对象,数组)进行拷贝时候的说法。然后说明一下拷贝,这里的拷贝指的你想创建一个新的数据b,这个数据b除了名字不一样和其他的原数据a一模一样,注意两个点:
(1)创建一个新数据b,b和a是两个独立的个体。
(2)a和b的所包含的内容完全一致。
再然后举个例子说明一下深拷贝、浅拷贝和等号负值的区别。

话说
尼古拉斯赵四是象牙山响当当的人物,舞步独步天下。
于是乎人们为他起了各种绰号,亚洲舞王、象牙山郭富城等等。亚洲舞王是赵四的一个代号,就像他的本名赵四一样,都可以指代他。换句话来说,赵四、亚洲舞王、象牙山郭富城指的是同一个人,你去刘老根大舞台说找赵四、找尼古拉斯赵四、找亚洲舞王,最终找到的都是同一人。当你为赵四起名亚洲舞王的过程,就相当于等号负值

let ZhaoSi = {
	name: 'zhaosi',
	hobby: 'dance',
	mind:[‘舞步1,‘舞步2,‘舞步3]};
//等号赋值(给赵四起名亚洲舞王)
let AsiaDancer = ZhaoSi;

这个过程中只有赵四一人,没有新的人产生,你只是为他起了个能找到他的新名字。即:此时AsiaDancer 中保存的是ZhaoSi这个对象的引用,AsiaDancer和ZhaoSi两者指的是同一个对象。
现在出现了一个问题:假若有一天赵四这个人会不跳舞了,他喜好变为唱歌,此时不管是你使用赵四还是亚洲舞王找到的这个人,都无法从他的hobby中找到dance了,即:

let ZhaoSi = {
	name: 'zhaosi',
	hobby: 'dance',
	mind:[‘舞步1,‘舞步2,‘舞步3]};
//等号赋值(给赵四起名亚洲舞王)
let AsiaDancer = ZhaoSi;
// 修改爱好为唱歌
ZhaoSi.hobby = 'sing';
console.log(ZhaoSi.hobby); // sing
console.log(AsiaDancer.hobby); // sing

可以看到console.log的输出结果均为sing,因为AsiaDancer和ZhaoSi所指向的数据是同一个对象,这个对象本身改变了。
··································································································
然后看第二段故事-----浅拷贝
话说,
赵四凭借着卓越的舞技,成为很多娱乐公司的红人。为了更好分发挥赵四的舞蹈天分,科学家为赵四打造了几台仿生躯体(就叫做复制体1,复制体2…等),这些躯体严格按照赵四相貌身材去打造,从外观上看和赵四一模一样,这些机器人没有大脑,无法读取到各种舞蹈的舞步,但是他们的内部有接受信号的芯片,可以连接到赵四的大脑,赵四可以通过自身大脑中的各种舞步记忆来控制机器复制体们跳出跟他自己一样卓越的舞步,从此可以让多个地区的观众一饱舞王的眼福。注意,这个故事中的机器人都是赵四的复制体,他们是半独立的,他们的外表和躯体都和赵四完全一样,但是没有内部,内部其实都还是链接到赵四本人的。注意:此时的复制体都是新的个体,但是他们和主体赵四和存在联系,不能独立的进行舞蹈行为。这个过程类似于浅拷贝。 浅拷贝真正拷贝复制的是引用数据类型的第一层,再往下便不会复制(复制体只有赵四一样的躯体和外表,并没有赵四的大脑和脑中的各种舞步)。此时若是修改数据除了第一层以外内容(比如第二层),所有复制数据都会受影响,但修改第一层的内容不会,上代码:

let ZhaoSi = {
	name: 'zhaosi',
	hobby: 'dance',
	mind:[‘舞步1,‘舞步2,‘舞步3]};
// 浅拷贝方法
function shallowCopy(src) {
    var dst = {};
    for (var prop in src) {
        if (src.hasOwnProperty(prop)) {
            dst[prop] = src[prop];
        }
    } 
    return dst;
}
var copyMan1 = shallowCopy(ZhaoSi ); // 使用浅拷贝创建复制体1

copyMan1.hobby = 'sing';
console.log(ZhaoSi.hobby); // dance  未变
console.log(copyMan1.hobby); // sing 变了

copyMan1.mind[1]= '舞步10086';
console.log(ZhaoSi.mind); // [‘舞步1’,‘舞步10086’,‘舞步3’]  变了
console.log(copyMan1.mind); // [‘舞步1’,‘舞步10086’,‘舞步3’]  变了

上述代码中我们可以清楚的看到两次修改后变化的不同。
··································································································
第三段故事-----深拷贝
科学家们觉得故事二中的复制体们并不完美,于是他们利用超越复制技术的更高深的技术,制造了几个真正的克隆体(姑且叫做赵四1、赵四2…)。这几个克隆体不仅从外表身躯上和赵四一致,连大脑、思考和灵魂都与赵四一样。赵四1、赵四2这些克隆体是全新的个体,他们可以独立的跳舞,他们拥有赵四所有的舞步记忆和天赋,而且当赵四1学习了新舞步10010替换掉舞步3后,并不影响赵四和赵四2继续跳舞步3,这个过程类似于深拷贝,看代码:

let ZhaoSi = {
	name: 'zhaosi',
	hobby: 'dance',
	mind:[‘舞步1,‘舞步2,‘舞步3]};
// 深拷贝方法
function deepClone(obj) {
    let copy = obj instanceof Array? [] : {};
    for (let i in obj) {
        if (obj.hasOwnProperty(i)) { // 过滤掉继承属性
            copy[i] = typeof copy[i] === 'object'? deepClone(copy[i]) : copy[i];
        }
    } 
    return copy;
}
var cloneMan1 = deepClone(ZhaoSi ); // 使用深拷贝创建克隆体1
var cloneMan2 = deepClone(ZhaoSi ); // 使用深拷贝创建克隆体2

cloneMan1.hobby = 'sing';
console.log(ZhaoSi.hobby ); // dance  未变
console.log(cloneMan1.hobby ); // sing  变了
console.log(cloneMan2.hobby ); //dance  未变

cloneMan1.mind[2] = '舞步10010';
console.log(ZhaoSi.mind); // [‘舞步1’,‘舞步2’,‘舞步3’]  没变
console.log(cloneMan1.mind); // [‘舞步1’,‘舞步2’,‘舞步10010’]  变了
console.log(cloneMan2.mind); // [‘舞步1’,‘舞步2’,‘舞步3’]  没变

可以看到无论是对cloneMan1中第一层内容hobby的修改,还是对第二层内容mind中舞步的修改,都不会影响到赵四本身和其他克隆体的内容。每一个克隆体都是独立且完整的赵四复刻体。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。
这时有同学会问这是什么原理呢?在实际情况中,当你创建一个引用数据类型(比如一个对象a)的时候,系统会在堆中开辟一块内存从来存储对象a,在栈中会有对象a的引用。当你进行深拷贝的时候,即var b = deepClone(a );执行后,系统会新开辟一块内存,新开辟的内存中保存着和之前 a对象中所有内容,然后让b去指向这个新开辟的内存。此时a和b指向的是两个不同的内存区域,所以他们之间相互独立,只是存储的信息相同而已。同样,等号赋值和浅拷贝二层及以上层内容仅仅是拷贝了一个引用,并没有开辟新的内存区域来存放数据。

浅拷贝实现方法

1、Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "kobe", b: 39} };
var initalObj = 
Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); //wade

注意:当object只有一层的时候,是深拷贝

let obj = {
    username: 'kobe'
};
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj); //{username: "kobe"}

2、Array.prototype.concat()

let arr = [1, 3, {    username: 'kobe'    }];
let arr2=arr.concat();    

深拷贝实现方法

1、JSON.parse(JSON.stringify())

let arr = [1, 3, {    username: ' kobe'}];
let arrCopy = JSON.parse(JSON.stringify(arr));

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

2、手写递归方法
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。

//定义检测数据类型的功能函数
    
function checkedType(target) {
    return Object.prototype.toString.call(target).slice(8, -1)
}
    
//实现深度克隆---对象/数组
    
function clone(target) {
      
    //判断拷贝的数据类型
      
    //初始化变量result 成为最终克隆的数据
      
    let result, targetType = checkedType(target)
      
    if (targetType === 'object') {
        result = {}
     } else if (targetType === 'Array') {
        result = []
      } else {
        return target
      }
      
    //遍历目标数据
    for (let i in target) {
        //获取遍历数据结构的每一项值。
        let value = target[i]
        //判断目标结构里的每一值是否存在对象/数组
        if (checkedType(value) === 'Object' ||
          checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
          //继续遍历获取到value值
          result[i] = clone(value)
        } else { //获取到value值是基本的数据类型或者是函数。
          result[i] = value;
        }
      }
      return result
}

如有错误,欢迎指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值