前端经典问题总结——拷贝

348 阅读2分钟

js中常会出现数组和对象的拷贝,拷贝一般是对引用值而言的。

1.浅拷贝

特点: 浅拷贝只是将栈内存里的值拷贝给新对象,对于引用值而言它存在栈内存的只是它的存储数据的堆内存地址。所以浅拷贝后引用类型的值会相互影响。

实现浅拷贝的方法

(1)常规遍历赋值

function clone(source){
    let target = Object.prototype.toString.call(source)="[object Object]"?{}:[]
    for(let key in source){
        target[key] = source[key]
    }
    return target
}

(2)Object.assign()(适用于对象)

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
 let source = {key:"test"};
 let target = Object.assign({},source)

(3)Array.prototype.concat()(适用于数组拷贝)

var arr = [1,test:{a:"1"}];
var arr1 = arr.concat()

(4)Array.prototype.slice()(适用于数组)

var arr = [1,test:{a:"1"}];
var arr1 = arr.slice()

2.深度拷贝

特点: 深度拷贝是值的完全拷贝,拷贝前后两个引用值互不影响

(1)乞丐版深度拷贝

let obj = {a:b{c:1}};
let obj1 = JSON.parse(JSON.stringify(obj))

不足点:

a.无法实现对函数 、Date、RegExp等特殊对象的克隆
b.会抛弃对象的constructor,所有的构造函数会指向Object
c.对象有循环引用,会报错

(2)只考虑普通对象和数组的深度拷贝

// 深度拷贝
function deepClone(source){
    if(source===null||typeof source!=="object"||typeof source!=="function"){
        return source
    }
    var target = source instanceof Array?[]:{}
    for(key in source){
        if(source.hasOwnProperty(key)){
            target[key] = typeof source[key] === "object"?deepClone[source[key]]:source[key]
        }
    }
    return source;
}

(3)考虑特殊对象的深拷贝

// 获取当前属性值的类型
function getType(ele){
    var template = {
		"[object Array]":"array",
		"[object Object]":"object",
		"[object Number]":"number",
		"[object Undefined]":"undefined",
		"[object Boolean]":"boolean",
		"[object String]":"string",
		"[object Date]":"Date",
		"[object RegExp]":"RegExp"
	}
	if(targ === null){
		return "null";
	}else if(typeof targ == "object"){
		var str = Object.prototype.toString.call(targ);
		return template[str];
	}else{
		return typeof targ;
	}
}

// 获取正则表达式的flags(可能有组合的flag)
function getRegFlag(reg){
    var flags = '';
    if (reg.global) flags += 'g';
    if (reg.ignoreCase) flags += 'i';
    if (reg.multiline) flags += 'm';
    return flags;
}

// 深度拷贝
function deepClone(oldObj){
    var newObj;
    var type = getType(oldObj)
    switch(type){
        case "boolean":
        case "number":
        case "string":
        case "undefined":
        case "null":{
            return oldObj;
            break;
        }
        case "symbol":{
            return Symbol(Symbol.keyFor(oldObj).toString());
            break;
        }
        case "regExp":{
            newObj = new RegExp(oldObj.source, getRegFlag(oldObj));
            if (oldObj.lastIndex) newObj.lastIndex = oldObj.lastIndex;
            break;
        }
        case "date":{
            newObj = new Date(oldObj.getTime());            
            break;
        }
        case "function":{
            newObj = eval(oldObj.tostring());
        }
        case "array":{
            newObj = [];
            for(var prop in oldObj){
                if(oldObj[prop]!==oldObj){ // 處理循環引用情況
                    newObj[prop]=deepClone(old[prop]);
                }else{  // 若循環引用則將新對象直接賦值給子元素
                    newObj[prop] = newObj;
                }
            }
	        break;
        }
        default:{
            var proto = Object.getPrototypeOf(oldObj);
            newObj = Object.create(proto);
            for(var prop in oldObj){
                if(oldObj[prop]!==oldObj){  // 處理循環引用情況
                    newObj[prop]=deepClone(old[prop]);
                }else{ // 若循環引用則將新對象直接賦值給子元素
                    newObj[prop] = newObj;
                }
            }
            break;
        }
    }
    return newObj;
}