聊一聊JavaScript的深拷贝

JS里的深拷贝

前言

很多知识深挖会让人有豁然开朗的感觉,所以建议平时学习尽量也要深挖(遇到哪个词模糊就去查,一直到搞懂你原本要查的知识点里的每一句话),你会发现收获超级多!

对于学习前端的人来说,js中的深拷贝都很熟悉,我今天也来扒一扒它!

首先,明确概念,什么是深拷贝?

有时候,我们有一堆数据,需要对它进行一系列的操作,但是还不能改变它的原始数据,这时候你会想到把它拷贝一份,对备份进行操作,但是你要知道js中的数据类型不全都是可以直接复制拷贝的
OK,那就来复习一下JS中的数据结构:👇👇

JS中的数据类型:

数据类型分为两大类:原始数据类型引用数据类型

原始数据类型:(6 种原始类型,使用 typeof 运算符检查:)

  • Number ———64位双精度浮点型的数字数据类型 typeof instance === “number”
  • String ——字符串 typeof instance === "string
  • Boolean ——布尔值 typeof instance === “boolean”
  • undefined —— 一个声明未定义的变量的初始值,或没有实际参数的形式参数 — typeof instance === “undefined”
  • null —— 表示不存在或者引用地址为空 typeof instance === "object"
  • Symbol —— ES6中新增的原始数据类型 typeof instance === “symbol”

引用数据类型: Object.prototype.toString.call ( ) 检测

  • 对象 [ object Object ]
  • 数组 [ object Array ]
  • 正则 [ object RegExp]
  • 时间 [ object Date]
  • 函数 [ object Function]

好了,数据类型复习完了,大家应该也知道,引用类型的数据不能直接以赋值形式进行拷贝的,因为堆栈原因……(说烂了)看图!👇💡

在这里插入图片描述

所以,newObj 对房间1的所有操作,都会影响到obj 后续对房间1的操作,那么想个办法吧!

我再弄一间房,房间2,我把房间2布置的和房间1一模一样,只有钥匙不一样而已,这样就可以模仿对房间1的操作了

说干就干!

直接上代码了哈!(手写的简low版,但是对于理解还是够的)

function clone(data) {
    let type = typeof data;//typeof判断不是很精确,函数的typeof值为“function”
    if (type != "object") {
        return data;
    } else if (type == "object") {//
        let fina = Object.prototype.toString.call(data);
        const res = fina == "[object Array]" ? [] : {}
        for (let key in data) {
            if (data.hasOwnProperty(key)) {//要是需克隆对象自身的属性,才去拷贝
                res[key] = clone(data[key]);
            }
        }
        return res;
    }
}
let o = {
    a: "outer",
    b: {
        d: "inner"
    },
    c: [1, 3, 3, {
        e: "inner inner"
    }],
    y: function() {
        console.log("func")
    },
    g: Infinity,
};
let obj = clone(o);
obj.b.d = "change inner";
console.log(obj)
console.log(o)

但是这个版本不能拷贝函数、正则、时间、null这些

来看看大佬的完美版的深克隆(我认为各种情况都考虑到了)👇👇👇

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;
    }
}

export 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;
}

点击前往大佬的文章

以上是对深拷贝的自我实现,那有没有什么原生方法可以进行深克隆呢?(其实也算不上,问题太多了)

原生方法:

1. JSON对象的方法:这个问题太多了(每种问题对应的实际例子,大家自己试哈)

  1. 取不到值为 undefined 的 key
  2. NaN 和 无穷大,无穷小转变为 null
  3. 取不到原型的内容
  4. date 对象转变为 date 字符串
  5. 值为symbol类型会丢失
  6. 循环引用会报错

2. Object.assign()
3. 数组的splice 、 slice
4. 拓展运算符

以上三类方法,都有一个共同的毛病,就是只能处理只有一层结构的引用类型的数据
?????我这**说的是人话????
意思就是,比如:数组里面只有原始类型的数据,对象也是,里面再有其他引用类型的数据,就是两层了


当对象或数组里面还有引用类型的数据,那么里面的引用类型的数据就是浅拷贝,这个看一个例子(其他方法类似):👇👇👇

在这里插入图片描述

好了,相信看完,你又会成长一点,我们一起加油,一起奔向诗和远方!

看都看完了,给个一键三连加关注呗😁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

summer·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值