Skip to main content

深拷贝和浅拷贝

一、堆与栈

栈(stack)为自动分配的内存空间,它由系统自动释放;而堆(heap)则是动态分配的内存,大小不定也不会自动释放。

二、基本数据类型

基本数据类型主要是:undefinedbooleannumberstringnull。基本数据类型存放在栈中,存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。

基本数据类型的值不可改变

javascript 中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。对数字和布尔值来说显然如此 —— 改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来假改字符串中的字符。实际上,javascript 是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值

// 基本数据类型的值不可改变
var str = '123';
str[1] = 4;
console.log(str); // 123

三、引用类型

引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配,例如。

引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。

四、赋值

引用类型的赋值只能算得上是“引用”,并不算真正的浅复制。

五、浅拷贝

1. Object.assign

Object.assign实现的还是一个浅拷贝

const person = {
name: 'andy',
age: 12,
};

const newPerson = Object.assign({}, person);

2. slice()

const fxArr = ['One', 'Two', 'Three'];
const fxArrs = fxArr.slice(0);
fxArrs[1] = 'love';
console.log(fxArr); // ["One", "Two", "Three"]
console.log(fxArrs); // ["One", "love", "Three"]

3. concat()

const fxArr = ['One', 'Two', 'Three'];
const fxArrs = fxArr.concat();
fxArrs[1] = 'love';
console.log(fxArr); // ["One", "Two", "Three"]
console.log(fxArrs); // ["One", "love", "Three"]

4. 拓展运算符

const fxArr = ['One', 'Two', 'Three'];
const fxArrs = [...fxArr];
fxArrs[1] = 'love';
console.log(fxArr); // ["One", "Two", "Three"]
console.log(fxArrs); // ["One", "love", "Three"]

5. 循环复制

浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。

function shallowCopy(obj) {
const result = {};

for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key];
}
}

return result;
}

六、深拷贝

1. _.cloneDeep()

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

2. jQuery.extend()

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

3. JSON.stringify()

const obj2 = JSON.parse(JSON.stringify(obj1));

但是这种方式存在弊端,会忽略undefinedsymbol函数

const obj = {
name: 'A',
name1: undefined,
name3: function () {},
name4: Symbol('A'),
};
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

4. 循环递归

深拷贝是对对象以及对象的所有子对象进行拷贝。

const slice = Array.prototype.slice;
const isArray = (arr) =>
Object.prototype.toString.call(arr) === '[object Array]';
const isObject = (obj) =>
Object.prototype.toString.call(obj) === '[object Object]';

function clone(target, source) {
for (let key in source) {
if (isArray(source[key]) || isObject(source[key])) {
if (isArray(source[key]) && !isArray(target[key])) {
target[key] = [];
}
if (isObject(source[key]) && !isObject(target[key])) {
target[key] = {};
}

clone(target[key], source[key]);
} else if (source[key] !== undefined) {
target[key] = source[key];
}
}
}

/**
* 深度拷贝
*/
function deepClone() {
const args = slice.call(arguments);
const target = isArray(args[0]) ? [] : {};

args.forEach(function (arg) {
clone(target, arg);
});

return target;
}

小结

前提为拷贝类型为引用类型的情况下:

  • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址

  • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址