JavaScript存在两大数据类型:
其中
栈区:会自动分配内存空间,自动释放,占用固定大小空间,存放基本类型。
堆区:会动态分配内存空间,大小不固定且不会自动释放,存放引用类型。 基本数据类型(栈区)
补充:内存中的栈区(stack)和堆区(heap)
“基本数据类型”在内存中占有固定大小的空间,通过按值访问。“引用数据类型”在内存中大小不固定,所以其在栈中只存放堆内存地址(内存地址是大小固定的),指向的却是堆中的对象,所以它是按引用访问的。假如需要获取“引用数据类型”中的变量,需要先访问栈内存中的地址,然后根据该地址才能获取到相对应的值。
对于基本数据类型而言并没有 “深浅拷贝” 的区别,对于基本数据类型而言并没有 “深浅拷贝” 的区别
概括:当你在目标对象中修改一个值的时候,看对原对象是否有影响。如果有影响那就是浅拷贝,如果没有那就深拷贝。
浅拷贝会在栈中开辟一个新的内存空间,将原对象一级中的“基本数据类型”复制一份到新的内存空间,所以相互不影响。当对象中有“引用类型”时,它只能拷贝“引用类型”在堆内存中的地址,所以赋值后会影响原对象的值。
存在浅拷贝现象的有:
使用说明:Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
//如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性
const object1 = {
a: 1,
b: 2,
c: 3
};
const object2 = Object.assign({c: 4, d: 5}, object1);
console.log(object1) // { a: 1, b: 2, c: 3 }
console.log(object2) // { c: 3, d: 5, a: 1, b: 2 }
浅拷贝说明:
//当定义的对象只有基本类型时,该方法就是深拷贝。
let a = {
name: 'sweet',
age: 18
};
let b = Object.assign({}, a);
b.name = "哈哈";
console.log(a); // a = {name: 'sweet', age: 18};
console.log(b); // b = {name: '哈哈', age: 18};
//当定义的对象中有引用类型的时,该方法就是浅拷贝。
let a = {
name: 'sweet',
age: 18,
eat:{
type: '苹果',
price: 18,
}
};
let b = Object.assign({}, a);
b.name = "哈哈";
b.eat.type = "西瓜";
b.eat.price = 30;
console.log(a); // a = {name: 'sweet', age: 18, eat:{ type: '西瓜', price: '30'}};
console.log(b); // b = {name: '哈哈', age: 18, eat:{ type: '西瓜', price: '30'}};
//我们通过使用扩展运算符(...)也能实现深拷贝。该方法和上述方法一样只能用于深拷贝第一层的值,当拷贝第二层的值时,仍是引用同一个内存地址。
//深拷贝
let a = {
name: 'sweet',
age: 18
};
let b = {...a};
b.name = "哈哈";
console.log(a); // a = {name: 'sweet', age: 18};
console.log(b); // b = {name: '哈哈', age: 18};
//浅拷贝
let a = {
name: 'sweet',
age: 18,
eat:{
type: '苹果',
price: 18,
}
};
let b = {...a};
b.name = "哈哈";
b.eat.type = "西瓜";
b.eat.price = 30;
console.log(a); // a = {name: 'sweet', age: 18, eat:{ type: '西瓜', price: '30'}};
console.log(b); // b = {name: '哈哈', age: 18, eat:{ type: '西瓜', price: '30'}};
同上,只能用于深拷贝第一层的值,当拷贝第二层的值时仍是引用同一个内存地址。
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
使用第三方函数库 lodash
const _ = require('lodash');
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
const $ = require('jquery');
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
对象深拷贝
//该方法可以实现深拷贝。 但是需要注意
1:会忽略undefined,Symbol,函数。
2:在处理new Date() 会出错
3:循环引用会出错
4:不能处理正则,拷贝的是一个空对象
5:继承的属性会丢失
一句话概括:可以转成 JSON 格式的对象才能使用这种方法。
let a = {
name: 'sweet',
age: 18,
fn: function(){},
from: undefined,
to: Symbol('深圳'),
nums: /'g'/,
eat:{
type: '苹果',
price: 18,
}
};
let b = JSON.parse(JSON.stringify(a));
b.name = "哈哈";
b.eat.type = "西瓜";
b.eat.price = 30;
console.log(a); // a = {name: 'sweet', age: 18, nums: /'g'/, fn: function(){}, eat:{ type: '苹果', price: '18'}};
console.log(b); // b = {name: '哈哈', age: 18, nums: {}, eat:{ type: '西瓜', price: '30'}};
数组深拷贝
let arr = [1, 2, [1]];
let newArr = JSON.parse(JSON.stringify(arr));
newArr[2][0] = 10;
console.log(arr, newArr); // [1, 2, [1]] [1, 2, [10]]
let arr1 = [1, 2, [1]];
let newArr1 = JSON.parse(JSON.stringify(arr1));
newArr1[2][0] = 10;
console.log(arr1, newArr1); // [1, 2, [1]] [1, 2, [10]]
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}