javascript中的深拷贝与浅拷贝

时间:2022-02-14 10:59:50

javascript中的深拷贝与浅拷贝

基础概念

在了解深拷贝与浅拷贝的时候需要先了解一些基础知识

核心知识点之 堆与栈

栈(stack)为自动分配的内存空间,它由系统自动释放;

堆(heap)则是动态分配的内存,大小不定也不会自动释放。

基本数据类型存放在栈中,基本数据类型主要是:undefined,boolean,number,string,null

在 js 中我们对基础数据类型的操作主要是赋值为主

引用类型存放在堆中 引用类型 (object) 其变量实际上

    var a = [1,2,3];
var b = [1,2,3];
console.log(a === b); // false
console.log(a == b); // false

其内存位置不同,尽管内容相同,但本质却不同,可简单的认为是同一户型的两套房子,内部一模一样但并不是同一套房子

传值与传址

基本数据类型的赋值操作,就是只在内存中开辟新的栈内存,将值赋到新的栈中,

而引用类型的赋值是传址 ,即改变指针的指向,故如果两个变量指向同一对象的话,两者间会相互影响

浅拷贝

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

ES6 当中提供了 Object.assign(target, obj) 方法来进行对拷贝,这一方法进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身,Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

同时 assign() 方法应当注意以下几点

  1. 不会拷贝对象继承的属性、不可枚举的属性、属性的数据属性/访问器属性
  2. 可以拷贝 Symbol 类型
  3. Object.assign()拷贝的是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值,

来看个例子:

var a = {a : 'old', b : { c : 'old'}}
var b = Object.assign({}, a)
b.a = 'new'
b.b.c = 'new'
console.log(a) // { a: 'old', b: { c: 'new' } }
console.log(b) // { a: 'new', b: { c: 'new' } }

如上面例子,当拷贝的源对象的属性值是一个对象时,拷贝的只是对象的引用值,因此当修改属性值的时候两个对象的属性值都会发生更新

深拷贝

深拷贝是在浅拷贝的基础上,对对象更深的层级进行浅拷贝,也就是递归拷贝的方法

简单的深拷贝

/* ================ 深拷贝 ================ */
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
} if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
    // 内部方法:用户合并一个或多个对象到第一个对象
// 参数:
// target 目标对象 对象都合并到target里
// source 合并对象
// deep 是否执行深度合并
function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
//isPlainObject()函数用于判断指定参数是否是一个纯粹的对象
// source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {} // source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
if (isArray(source[key]) && !isArray(target[key]))
//isArray() 用于确定传递的值是否是一个 Array。
target[key] = []
// 执行递归
extend(target[key], source[key], deep)
}
// 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
else if (source[key] !== undefined) target[key] = source[key]
} // Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
var deep, args = slice.call(arguments, 1);
//slice.call()能将具有length属性的对象转成数组
//第一个参数为boolean值时,表示是否深度合并
if (typeof target == 'boolean') {
deep = target;
//target取第二个参数
target = args.shift()
}
// 遍历后面的参数,都合并到target上
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}

同时在 C++ 当中可以作以类比来提升我们对这一概念的认知

【浅拷贝】只是增加了一个指针,指向已存在对象的内存。

【深拷贝】是增加了一个指针,并新开辟了一块空间,让指针指向这块新开辟的空间。

【浅拷贝】在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误

根据以上概念对深浅拷贝作简单的总结

在 javascript 不允许直接访问保存在堆内存中的对象,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值;

深拷贝:将 B 对象拷贝到 A 对象中,包括 B 里面的子对象,

浅拷贝:将 B 对象拷贝到 A 对象中,但不包括 B 里面的子对象

暂时以上