天天看点

深拷贝与浅拷贝的js实现方式

深拷贝、浅拷贝的操作目标都是对象,对象的作为引用类型,它的数据存放在堆内存中,而数据指针存放在栈内存中,当访问引用数据时,会先从栈内存中获取指针,通过指针在堆内存中找到所需数据。

深拷贝

1. JSON实现深拷贝

JSON 是基于 JavaScript 的语法,用来序列化对象、数组、数值、字符串、布尔值和 null,,但它不是 JavaScript 的子集。它拥有 JSON.parse() 和 JSON.stringify() 两个方法,分别用来解析 JSON 字符串和序列化数据。

通过 JOSN.stringify(obj)将数据序列化为字符串,然后重新解析该字符串,分配堆内存和栈内存来分别存储数据和指针,从而实现深拷贝。

注意,该方法不能复制 function、正则和 symbol类型,同时在循环引用时也会报错

比方说,一个对象的不同属性或者子对象等等的属性指向同一个引用类型的数据,在 JSON 解析后将不再指向同一个地址了,而是按照实际重新分配了不同的引用地址

var sameObj = {'a': 1};
var data = {'b': sameObj, 'c':{'d': 1, 'e':sameObj}};
data['b'] === data['c']['e'];	//	true,	因为指向的是同一个引用类型对象

var nowData = JSON.parse(JSON.stringify(data));
nowData ['b'] === nowData ['c']['e'];	//	false, 序列化时这两个数据重新分配了不同的地址了
           
2. 递归实现深拷贝

创建一个对象,然后遍历目标对象的所有结点,将对应节点的数据在新对象上重新分配地址,尤其是类型为对象的数据,保证每一个节点上都是重新分配过的地址,从而实现深拷贝

function checkType(data){	//	用于判断数据类型
    return Object.prototype.toString.call(data).slice(8, -1);
}

function deepCopy(data){
    var originObjArr = [];  //  用于存放原始节点的数据
    var resultObjArr = [];  //  用于存放新建节点的数据

    function _deepCopy(node){
        var result;
        //  获取该节点在存放原始节点数组里的位置
        var index = originObjArr.indexOf(node);
        if(checkType(node) == 'Object'){
            //  如果在用于存放原始节点的数组中可以直接用新建节点来返回
            if(index > -1){
                return resultObjArr[index];
            }else{
                result = {};
                //  如果是该节点不在存放原始节点的数组里就push进去
                //  并把新建节点也push进存放新建节点的数组
                originObjArr.push(node);
                resultObjArr.push(result);
                //  递归调用
                for(var i in node){
                    //  排除从原型链上遍历到的属性
                    if(node.hasOwnProperty(i)) {
                        result[i] = _deepCopy(node[i])
                    }
                }
                return result;
            }
        }else{
            if(checkType(node) == 'Array'){
                if(index > -1){
                    return resultObjArr[index];
                }else {
                    result = [];
                    originObjArr.push(node);
                    resultObjArr.push(result);
                    for (var i = 0; i < node.length; i++) {
                        result[i] = _deepCopy(node[i]);
                    }
                    return result;
                }
            }else{
                //  既不是数组也不是对象就直接返回该类型数据
                return node;
            }
        }
    }

    return _deepCopy(data);
}
           

这个方法对于除数组和对象的其它引用类型没有处理,另外原型链上的属性也没有获取

浅拷贝

浅拷贝是拷贝目标对象里面的数据,但是不拷贝目标对象里面的子对象

function checkType(data){	//	用于判断数据类型
    return Object.prototype.toString.call(data).slice(8, -1);
}

function shallowCopy(data){
    var result;
    if(checkType(data) == "Object"){
        result = {};
        for(var i in data){
            //  排除从原型链上遍历到的属性
            if(data.hasOwnProperty(i)) {
                result[i] = data[i];
            }
        }
        return result;
    }else{
        if(checkType(data) == 'Array'){
            result = [];
            for (var i = 0; i < data.length; i++) {
                result[i] = data[i];
            }
            return result;
        }else{
            return data;
        }
    }
}
           

es6 提供了新方法 Object.assign(),用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。需要注意的是,该方法IE不支持,需要添加兼容Polyfill。

//	target 目标对象。
//	sources 源对象
Object.assign(target, ...sources)
           

关于数组浅拷贝可以用些数组的原生方法

1. slice 方法

slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。

2. concat 方法

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

其它的方法这里就不一一赘述了

深拷贝与浅拷贝的js实现方式