javascript之深浅拷贝

135 阅读3分钟

数组的浅拷贝

concat()、slice()、Array.from()方法返回一个新数组的特性来实现拷贝。如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。

var arr = [1,  {a: 'a'}];
var arr2 = arr.concat();
var arr3 = arr.slice();
var arr4 = Array.from(arr);

arr[1].a = 'b';

console.log(arr2);// [1,  {a: 'b'}]
console.log(arr3);// [1,  {a: 'b'}]
console.log(arr4);// [1,  {a: 'b'}]

Object.assign()方法可以实现浅拷贝,不只针对数组,也可以用在对象上

var arr = [1,  {a: 'a'}];
var arr5 = Object.assign([], arr);

arr[1].a = 'b';

console.log(arr5);// [1,  {a: 'b'}]

JSON.stringify深拷贝

然而使用这种方法会有一些隐藏的坑,它能正确处理的对象只有 Number, String, Boolean, Array,平面对象,即那些能够被json直接表示的数据结构。

1、如果里面有时间对象,结果时间将只是字符串的形式。而不是时间对象;

var date = JSON.parse(JSON.stringify( new Date()));
//"2019-10-08T14:11:47.842Z"

var obj = JSON.parse(JSON.stringify({date: new Date()}));
//{date: "2019-10-08T14:11:47.842Z"}

var arr = JSON.parse(JSON.stringify([new Date()]));
//["2019-10-08T14:11:47.842Z"]

2、如果里面有RegExp、Error对象,则序列化的结果将只得到空对象;

var re = JSON.parse(JSON.stringify({re: new RegExp()}));
//{re: {}}

var error = JSON.parse(JSON.stringify({error: new Error()}));
//{error: {}}

3、如果里面有函数,undefined,则序列化的结果会把函数或 undefined丢失

var fun = JSON.parse(JSON.stringify({fun: function(){}}));
//{}

var un = JSON.parse(JSON.stringify({un: undefined}));
//{}

4、如果里面有NaN、Infinity和-Infinity,则序列化的结果会变成null

var nan = JSON.parse(JSON.stringify({nan: NaN}));
//{nan: null}

var infinity = JSON.parse(JSON.stringify({in: Infinity}));
//{in: null}

5、只能序列化对象的可枚举的自有属性,例如:如果obj中的对象是有构造函数生成的,则使用stringify深拷贝后,会丢弃对象的constructor;

function Person(name) {
    this.name = name;
    console.log(name)
  }

  const liai = new Person('liai');

  const test = {
    name: 'a',
    date: liai,
  };
  
  // debugger
  const copyed = JSON.parse(JSON.stringify(test));
  test.name = 'test'
  console.error('ddd', test, copyed)

6、如果对象中存在循环引用的情况也无法正确实现深拷贝;

var a = {name : b};
var b = {name : a}
var c = JSON.parse(JSON.stringify({a, b}));
console.log(c);

自定义深拷贝,单项数据

const toString = Object.prototype.toString;
const MAX_SAFE_INTEGER = 9007199254740991;

function getTag(value){
    if(value == null){
        return value === 'undefined' ? '[object Undefined]' : '[object Null]';
    }
    return toString.call(value);
}
function isLength(value){
    return value === 'number' && value > -1 &&  value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
function isArrayLike(value){
    return value != null && typeof value !== 'function' && isLength(value.length)
}
function isArray(value){
    return (Array.isArray && Array.isArray(value)) || getTag(value) === '[object Array]';
}
function isObjectLike(value){
    //object(不包括null),不包括function
    return typeof value === 'object' && value !== null;
}
function isObject(value){
    //object(不包括null) + function
    let type = typeof value
    return value !== null && (type === 'object' || type === 'function');
}
function isPlainObject(value){
    if(getTag(value) != '[object Object]'){
        return false;
    }
    if(Object.getPrototypeOf(value) === null){
        return true;
    } 

    let proto = value;
    while (Object.getPrototypeOf(proto) !== null) {
        proto = Object.getPrototypeOf(proto);
    }
    return Object.getPrototypeOf(value) === proto;
}
function isFunction(value){
    if(!isObject(value)){
        return false;
    }
    const tag = getTag(value);

    return (tag == '[object Function]' 
        || tag == '[object AsyncFunction]' 
        || tag == '[object GeneratorFunction]' 
        || tag == '[object Proxy]');
}
function deepClone(value) {
    let type = getTag(value);
    let object;

    if (type === '[object Date]') {
        return new Date(value);

    } else if(type === '[object RegExp]'){
        let flags = '';
        flags += value.global ? 'g' : '';
        flags += value.ignoreCase ? 'i' : '';
        flags += value.multiline ? 'm' : '';
        return new RegExp(value.source, flags);

    } else if (isArray(value)) {
        object = [];
        for (let i = 0, len = value.length; i < len; i++) {
            object.push(deepClone(value[i]));
        }
    } else if (isObject(value)) {
        object = {};
        // 对原型上的方法也拷贝了....
        for (const key in value) {
            object[key] = deepClone(value[key]);
        }
    }  else {
        return value;
    } 
    return object;
}

注意:

支持基础类型数据、数组、平板对象、Date、RegExp的深拷贝

不支持函数、Error等对象的深拷贝

自定义深浅拷贝,参数可以为多项数据

function extend() {
    let options,
        src,
        copy,
        copyIsArray,
        clone,
        deep = false,
        target = arguments[0] || {},
        i = 1;

    if (typeof target == 'boolean') {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    if (typeof target !== 'object' && !isFunction(target)) {
        target = {};
    }
    for (let len = arguments.length; i < len; i++) {
        options = arguments[i];

        if (options == null) {//可以省掉
            continue;
        }

        for (let name in options) {
            src = target[name];
            copy = options[name];

            //防止循环引用
            if (target === copy) {
                continue;
            }

            //要递归的数据必须是对象或数组
            if (deep && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {

                if (copyIsArray) {//如果待复制数据是数组,目标属性不是数组,就将目标属性设置为数组
                    clone = src && isArray(src) ? src : [];
                } else {//如果待复制数据是对象,目标属性不是对象,就将目标属性设置为对象
                    clone = src && isPlainObject(src) ? src : {};
                }
                target[name] = extend(deep, clone, copy);

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

注意的情况:

解决循环引用的情况

对 Date, RegExp, 函数的深拷贝(不支持)

参考:

jerryzou.com/posts/dive-…

github.com/mqyqingfeng…

www.jianshu.com/p/b084dfaad…