js赋值,浅拷贝,深拷贝的区别及手写--判断两个对象是否相等--打印属性与对象展开不一致问题

一.数据类型

数据分为 基本数据类型和引用数据类型。
基本数据类型:
String、Number、Boolean、Null、Undefined、Symbol。基本数据类型是直接存储在栈中的数据。
引用数据类型:
Array、Object。引用数据类型在栈中存储的是该对象的引用地址,真实的数据存储在堆内存中

由于基本数据类型是直接存储的,所以如果我们对基本数据类型进行拷贝,然后修改新数据后,不会影响到原数据。
而当你对引用数据类型进行拷贝,然后修改新数据后,它就会影响到原数据。

二.浅拷贝、深拷贝与赋值的区别

赋值:引用地址的拷贝。修改赋值后的数据,引用数据类型,会影响到原数据。

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

		var obj = {
            id: 1,
            name: 'andy',
            msg: {
                age: 18
            }
        };

        var objTwo = obj;
        console.log(obj.id)  // 1
        objTwo.id = 9;
        console.log(obj.id)  // 9

深拷贝和浅拷贝的区别?
浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”。

  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
  • 但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

在这里插入图片描述

三.浅拷贝方法: 注意:当object只有一层的时候,是深拷贝

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

		var obj = { name: { a: "kobe", b: 39 }, age: 899 };
        var initalObj = Object.assign({}, obj);
        initalObj.name.a = "wade";
        initalObj.age = 233;
        console.log(obj.name.a); // wade
        console.log(obj.age); // 899 // 注意:当object只有一层的时候,是深拷贝

2. ES6扩展对象 newObj = { …obj }

		var objData = { name: { a: "kobe", b: 39 }, age: 899 };
        let newObj = { ...objData }
        newObj.name.a = "susu";
        newObj.age = 345;
        console.log(objData.name.a); // susu
        console.log(objData.age); // 899 // 注意:当object只有一层的时候,是深拷贝

**3.自定义函数 **

	function simpleCopy(initalObj) {
        var obj = {};
          for (var i in initalObj) {
              obj[i] = initalObj[i]
          }
          return obj;
      }

四.深拷贝方法:

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

  • 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
  • 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。这是因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。
	let arr = [1, 3, {  username: ' kobe'}];
    let arr4 = JSON.parse(JSON.stringify(arr));

2. 手写深拷贝方法 自己封装

function deepClone(obj) {
       // 判断要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,是对象的话进行对象拷贝
       let objClone = Array.isArray(obj) ? [] : {};
       if (obj && typeof obj === 'object') {
           // for ... in 会把继承的属性一起遍历
           for (let key in obj) {
               // 判断是不是自有属性,而不是继承属性
               if (obj.hasOwnProperty(key)) {
                   //判断obj子元素 是否为对象或数组, 如果是,递归复制
                   if (obj[key] && typeof obj[key] === 'object') {
                       objClone[key] = deepClone(obj[key]);
                   } else {
                       // 如果不是,简单复制
                       objClone[key] = obj[key];
                   }
               }
           }
       }
       return objClone;
   }
	//简单写法
   function deepClone(obj) {
       var target = {};
       for (var key in obj) {`在这里插入代码片`
           if (Object.prototype.hasOwnProperty.call(obj, key)) {
               if (typeof obj[key] === 'object') {
                   target[key] = deepClone(obj[key]);
               } else {
                   target[key] = obj[key];
               }
           }
       }
       return target;
  }

3 用工具库 lodash cloneDeep

五.如何判断两个对象是否相等?

两个Object类型对象,即使拥有相同属性、相同值,当使用 == 或 === 进行比较时,也不认为他们相等。
引用数据类型在js中对比的不是值,而是内存地址的对比,也就是说对比的是引用地址。

var obj1 = {
    name: "xiaoming",
    sex : "male"
}
var obj2 = {
    name: "xiaoming",
    sex : "male"
}
var obj3 = obj1;

console.log(obj1 === obj3); // true  地址相等
console.log(obj1 === obj2); // false  内存地址的不一样

console.log(JSON.stringify(obj1) === JSON.stringify(obj2))  // true

怎样判断两个对象在地址不一样的情况下,值是不是相等

5.1.方法一:通过JSON.stringify(obj)来判断两个对象转后的字符串是否相等

优点:用法简单,对于顺序相同的两个对象可以快速进行比较得到结果
缺点:这种方法有限制就是当两个对比的对象中key的顺序不是完全相同时会比较出错

对象中嵌套对象,那这个方法就解决不了了,我们需要引入递归和类判断系统了

	const tar1 = {
     	name:'kebi',
        age:33
    }
    const tar2 = {
        name:'kebi',
        age:33
    }
    const tar3 = {
        age:33,
        name:'kebi'
    }

    console.log(JSON.stringify(tar1) === JSON.stringify(tar2))  // true
    console.log(JSON.stringify(tar1) === JSON.stringify(tar3))  // false

5.2.方法二:函数检测

针对对象key顺序不一样

	function isEqual(first, second) {
       //通过 Object.keys() 的方法将对象名转为数组
       const arr1 = Object.keys(first)
       const arr2 = Object.keys(second)
       // 比较两个对象的长度,若长度不等,就没有必要进行后面的验证,直接返回false
       if (arr1.length !== arr2.length) return false;

       // 遍历对象,看对象的值是否相等
       for (let key in first) {
           if (first[key] !== second[key]) return false;
       }
       return true;
   } 
   console.log(isEqual(tar1, tar3)) // true

5.3.方法三:函数检测的基础上递归

处理对象的值也是对象

	const tar4 = {
	     name: 'kebi',
	     age: 33,
	     child: {
	         a: '999'
	     }
	 }
	 const tar5 = {
	     name: 'kebi',
	     age: 33,
	     child: {
	         a: '999'
	     }
	 }
	 function isEqual(first, second) {
	     //通过 Object.keys() 的方法将对象名转为数组
	     const arr1 = Object.keys(first)
	     const arr2 = Object.keys(second)
	     // 比较两个对象的长度,若长度不等,就没有必要进行后面的验证,直接返回false
	     if (arr1.length !== arr2.length) return false;
	
	     // 遍历对象,看对象的值是否相等  
	     for (let key in first) {
	         if (typeof first[key] === 'object' || typeof second[key] === 'object') {
	             if (!isEqual(first[key], second[key])) {
	                 return false;
	             }
	         } else {
	             if (first[key] !== second[key]) {
	                 return false;
	             }
	         }
	     }
	     return true;
	 }
	 console.log(isEqual(tar4, tar5))

5.4.lodash.isEqual()方法

检查对象的“值相等”的一个强大的方法,最好是依靠完善的测试库,涵盖了各种边界情况。lodash.isEqual()方法,用来比较好的处理深度对象的比较。

六.打印属性与对象展开不一致问题

先看问题:打印对象的属性,展开和不展开值不一样

	let obj ={
       id:23,
         info:{
             age:33,
             name:'QQ'
         }
     }
     // console.log(obj)
     // console.log(JSON.stringify(obj))
     // console.log(JSON.parse(JSON.stringify(obj)))
     console.log(obj.info)
     console.log(obj.info.name)
     console.log(obj)
     obj.id=88;
     obj.info.name = 'KK'

在这里插入图片描述

 简单来说,就是因为是否展开显示的问题。
 不展开,控制台默认显示当时对象obj的快照,而不是当前内存(堆内存)中obj.age的真实值;	
 展开,控制台不会显示obj的快照,而是会重新去内存中取obj.age的真实值。 
 对于Object等引用类型来说,都可能会出现上述异常打印输出。

console.log在打印引用数据类型的时候表现和我们预期不相符合,是因为打印的是引用数据类型的一个快照,
因为浏览器或者我们异步代码的原因,在快照之后修改了对应的内存空间的值,
所以等我们展开打印浏览器通过指针重新访问内存空间的时候,会获得最新的值,导致展开和不展开的表现不一致。

解决方案:

1.改为console.log(JSON.stringfy(obj)),把对象序列化到一个字符串中,,该输出方式会打印出当前对象的快照,也就能拿到类似同步下的理想结果。
2.最好的选择是在JavaScript 调试器中使用断点,而不要依赖控制台输出。

参考:
https://juejin.cn/post/6844903968586334221
https://blog.csdn.net/weixin_44019523/article/details/117047585

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值