JavaScript深拷贝和浅拷贝

1、认识JavaScript基本数据类型和引用类型

想要深入理解JavaScript深浅拷贝,我们必须先了解两个JavaScript数据类型-------基本数据类型和引用类型。

(1)基本数据类型

1、 基本数据类型的值在内存中占据固定大小的空间,并被保存在栈内存中。
2、当基本类型a的值赋值给另一个变量b时,会在内存中重新开辟一个空间并复制基本类型a的值给b。
3、基本数据类型的值不能添加属性。

javascript基本数据类型:Undefined、Null、Boolean、Number、String、Symbol

var x = 1;
var y = x;
console.log(y);//1
x = 3;
console.log(y);//1
console.log(x);//3
(2)引用类型

1、复杂的数据类型(比如对象)即使引用类型,名字在栈内存中,值是对象,保存在堆内存中。
2、引用类型的变量,包含是一个指向该对象的指针,而不是对象本身。
3、一个变量向另一个变量赋值引用类型的值时,实际上是复制了它的指针地址,因此两个变量最终都指向同一个对象。

var obj = {
	name:'大虫'
}
var obj2 = obj;
var num = 123;

栈内存和堆内存
从上图可以看出,我们把obj赋值给obj2后,赋值时只是赋值了obj指针的地址,他们指向同一个引用。

2、数组的浅拷贝

我们可以利用数组slice、concat方法中返回一个新数组的特性来实现拷贝。

var arrOne = [1,2,3,'one','two','three',undefined,null];
var arrTwo = arrOne.concat();
arrTwo[4] = 'six';
console.log(arrOne);//[1, 2, 3, "one", "two", "three", undefined, null]
console.log(arrTwo);//[1, 2, 3, "one", "six", "three", undefined, null]
var arrThree = arrOne.slice();
arrThree[4] = 'ten';
console.log(arrThree);//[1, 2, 3, "one", "ten", "three", undefined, null]

上面这种写法,看似没什么问题。但是如果数组里面嵌套了数组或者对象,拷贝就不灵敏了。

var arr = [1,2,{name:'大虫'},[3,4]];
var newArr = arr.concat();
arr[3][2] = 5;
console.log(arr);// [1,2,{name:'大虫'},[3,4,5]]
console.log(newArr);// [1,2,{name:'大虫'},[3,4,5]]

从这里我们可以看出,新旧数组都发生了变化,所以concat方法,克隆的并不彻底。

1、如果数组元素都是基本类型,就会拷贝数组里面的每一个元素,拷贝后与原数组互不影响。
2、如果数组元素里面含有引用类型(对象或数组),这样只会拷贝数组内引用类型的引用,所以我们无论是修改新数组还是旧数组,两者都会变化。

浅拷贝:我们把复制引用的拷贝方法称为新拷贝。
深拷贝:完全拷贝一个对象,即便里面嵌有引用类型,两者也互相分离,修改一个属性不会影响到另一个属性,我们叫做深拷贝。

3、数组的深拷贝
var arr1 = [1,2,{name:'大虫'},[3,4]];
var arr2 = JSON.parse(JSON.stringify(arr1));
arr1[2].age = 23;
console.log(arr1);//[1,2,{name:'大虫',age:23},[3,4]]
console.log(arr2);//[1,2,{name:'大虫'},[3,4]]

注意:该方式不能拷贝函数

var arr = [function(){console.log('fun1')}, {b: function(){console.log('fun2')}}]
var new_arr = JSON.parse(JSON.stringify(arr));
console.log(new_arr);//[null, {}]

1、undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。

2、对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

4、浅拷贝的实现
function shallowCopy(obj){
	// 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}
5、深拷贝的实现
function deepCopyObj(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

注意:因为深拷贝使用了递归,性能方面不如浅拷贝,在开发中还需要根据实际情况进行选择。

6、自己写一个extend(摘自冴羽博客,仅供学习使用)
function extend() {
    // 默认不进行深拷贝
    var deep = false;
    var name, options, src, copy;
    var length = arguments.length;
    // 记录要复制的对象的下标
    var i = 1;
    // 第一个参数不传布尔值的情况下,target默认是第一个参数
    var target = arguments[0] || {};
    // 如果第一个参数是布尔值,第二个参数是才是target
    if (typeof target == 'boolean') {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是对象,我们是无法进行复制的,所以设为{}
    if (typeof target !== 'object') {
        target = {}
    }

    // 循环遍历要复制的对象们
    for (; i < length; i++) {
        // 获取当前对象
        options = arguments[i];
        // 要求不能为空 避免extend(a,,b)这种情况
        if (options != null) {
            for (name in options) {
                // 目标属性值
                src = target[name];
                // 要复制的对象的属性值
                copy = options[name];

                if (deep && copy && typeof copy == 'object') {
                    // 递归调用
                    target[name] = extend(deep, src, copy);
                }
                else if (copy !== undefined){
                    target[name] = copy;
                }
            }
        }
    }
    return target;
};
7、最终版extend
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }
    proto = Object.getPrototypeOf(obj);
    if (!proto) {
        return true;
    }
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}


function extend() {
    // 默认不进行深拷贝
    var deep = false;
    var name, options, src, copy, clone, copyIsArray;
    var length = arguments.length;
    // 记录要复制的对象的下标
    var i = 1;
    // 第一个参数不传布尔值的情况下,target 默认是第一个参数
    var target = arguments[0] || {};
    // 如果第一个参数是布尔值,第二个参数是 target
    if (typeof target == 'boolean') {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是对象,我们是无法进行复制的,所以设为 {}
    if (typeof target !== "object" && !isFunction(target)) {
        target = {};
    }

    // 循环遍历要复制的对象们
    for (; i < length; i++) {
        // 获取当前对象
        options = arguments[i];
        // 要求不能为空 避免 extend(a,,b) 这种情况
        if (options != null) {
            for (name in options) {
                // 目标属性值
                src = target[name];
                // 要复制的对象的属性值
                copy = options[name];

                // 解决循环引用
                if (target === copy) {
                    continue;
                }

                // 要递归的对象必须是 plainObject 或者数组
                if (deep && copy && (isPlainObject(copy) ||
                        (copyIsArray = Array.isArray(copy)))) {
                    // 要复制的对象属性值类型需要与目标属性值相同
                    if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && Array.isArray(src) ? src : [];

                    } else {
                        clone = src && isPlainObject(src) ? src : {};
                    }

                    target[name] = extend(deep, clone, copy);

                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            }
        }
    }

    return target;
};
8、思考题
//题目一
var a = extend(true, [4, 5, 6, 7, 8, 9], [1, 2, 3]);
console.log(a) // ???
//题目二
var obj1 = {
    value: {
        3: 1
    }
}

var obj2 = {
    value: [5, 6, 7],

}

var b = extend(true, obj1, obj2) // ???
var c = extend(true, obj2, obj1) // ???

参考资料:
冴羽博客
小丁码农

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值