深浅拷贝解析

最近到了面试季节,做为前端的热门问题我们今天来盘一盘

说到深浅拷贝那就要说到JavaScript的数据类型基本数据类型和引用数据类型

基本数据类型

  1. String
  2. Number
  3. Boolean
  4. Null
  5. Undefined
  6. Symbol(new in ES 6)

引用数据类型

  1. Object
  2. Array
  3. Date
  4. RegExp
  5. Function

两者特点

  • 基本数据类型:直接存储在栈(stack)中的数据
  • 引用数据类型:存储的是该对象在栈中引用,真实的数据存放在堆内存里

简而言之就是

  • 浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
  • 深拷贝: 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

我们可以通过一段简单代码论证浅拷贝

  let data = 2;
  let data2 = data;
  data2 = 4
  console.log(data,data2)  // 2  4

  let data = {arr:1};
  let data2 = data;
  data2.arr = 4
  console.log(data.arr,data2.arr)  // 4  4

浅拷贝的实现方式:

  1. Object.assign() 方法: 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值
  2. **Array.prototype.slice():**slice() 方法返回一个新的数组对象,这一对象是一个由 begin和end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。
  3. 拓展运算符…:是对象第一层的深拷贝。后面的只是拷贝的引用值。

Object.assign()

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

**Array.prototype.slice()

const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
let data = animals.slice(2)
let data2 = animals.slice(2, 4)
let data3 = animals.slice(1, 5)
let data4 = animals.slice(-2)
let data5 = animals.slice(2, -1)
let data6 = animals.slice()

console.log(data);
// expected output: Array ["camel", "duck", "elephant"]

console.log(data2);
// expected output: Array ["camel", "duck"]

console.log(data3);
// expected output: Array ["bison", "camel", "duck", "elephant"]

console.log(data4);
// expected output: Array ["duck", "elephant"]

console.log(data5)
// expected output: Array ["camel", "duck"]

console.log(data6);
// expected output: Array ["ant", "bison", "camel", "duck", "elephant"]

拓展运算符…

let data = {
    name: "wang",
    flag: {
        title: "wangxiaohegn",
        time: "2022-03-14"
    }
}
let data2 = {...data};
data2.name = 'wang1';
console.log(data.name,data2.name) // wang  wang1
data2.flag.time = '2022-03-15'
console.log(data.flag.time,data2.flag.time) // 2022-03-15  2022-03-15

深拷贝的实现方式:

深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。

  • 乞丐版: JSON.parse(JSON.stringify(object)),缺点诸多(会忽略undefined、symbol、函数;不能解决循环引用;不能处理正则、new Date())
  • 基础版(面试够用): 浅拷贝+递归 (只考虑了普通的 object和 array两种数据类型)利用 JSON 对象中的 parse 和 stringify利用递归来实现每一层都重新创建对象并赋值

JSON.stringify()和JSON.parse()

let data = {
    name: "wang",
    flag: {
        title: "wangxiaohegn",
        time: "2022-03-14"
    }
}
let data2 = JSON.parse(JSON.stringify(data))
data2.name = 'wang1';
console.log(data.name,data2.name) // wang  wang1
data2.flag.time = '2022-03-15'
console.log(data.flag.time,data2.flag.time) // 2022-03-14  2022-03-15

方法1

function cloneDeep(target,map = new WeakMap()) {
  if(typeof target ==='object'){
     let cloneTarget = Array.isArray(target) ? [] : {};
     if(map.get(target)) {
        return target;
    }
     map.set(target, cloneTarget);
     for(const key in target){
        cloneTarget[key] = cloneDeep(target[key], map);
     }
     return cloneTarget
  }else{
       return target
  }
}

方法2

function deepClone(source){
 const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
  for(let keys in source){ // 遍历目标
	 if(source.hasOwnProperty(keys)){
		 if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
		   targetObj[keys] = source[keys].constructor === Array ? [] : {};
		   targetObj[keys] = deepClone(source[keys]);
		 }else{ // 如果不是,就直接赋值
		   targetObj[keys] = source[keys];
		 }
	 } 
  }
  return targetObj;
}
function clone (obj) {
   if(obj === null) return null
   if(typeof obj !== 'object') return obj;
   if(obj.constructor===Date) return new Date(obj);
   if(obj.constructor === RegExp) return new RegExp(obj);
   let newObj = new obj.constructor ();  //保持继承链
   for (var key in obj) {
     // 不遍历其原型链上的属性
     if (obj.hasOwnProperty(key)) {  
       let val = obj[key];
       // 使用arguments.callee解除与函数名的耦合
       newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; 
      }
   } 
   return newObj; 
};

终极方案参考:如何写出一个惊艳面试官的深拷贝

const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];


function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}

function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}

function getType(target) {
    return Object.prototype.toString.call(target);
}

function getInit(target) {
    const Ctor = target.constructor;
    return new Ctor();
}

function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    return result;
}

function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            if (param) {
                const paramArr = param[0].split(',');
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}

function cloneOtherType(targe, type) {
    const Ctor = targe.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
            return new Ctor(targe);
        case regexpTag:
            return cloneReg(targe);
        case symbolTag:
            return cloneSymbol(targe);
        case funcTag:
            return cloneFunction(targe);
        default:
            return null;
    }
}

function clone(target, map = new WeakMap()) {

    // 克隆原始类型
    if (!isObject(target)) {
        return target;
    }

    // 初始化
    const type = getType(target);
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target, type);
    } else {
        return cloneOtherType(target, type);
    }

    // 防止循环引用
    if (map.get(target)) {
        return map.get(target);
    }
    map.set(target, cloneTarget);

    // 克隆set
    if (type === setTag) {
        target.forEach(value => {
            cloneTarget.add(clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆map
    if (type === mapTag) {
        target.forEach((value, key) => {
            cloneTarget.set(key, clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆对象和数组
    const keys = type === arrayTag ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
        if (keys) {
            key = value;
        }
        cloneTarget[key] = clone(target[key], map);
    });

    return cloneTarget;
}

module.exports = {
    clone
};

写到最后的话大家不要忘记点赞收藏以防不备之需 发现新的好方法也可以写道评论区

请添加图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值