一.数据类型
数据分为 基本数据类型和引用数据类型。
基本数据类型:
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