【JavaScript】你了解深浅克隆吗


写在前面

数组的深浅克隆在前端开发中经常使用,面试也经常被问到,本篇文章将详细介绍深浅克隆。

深浅克隆

深浅克隆的定义和区别

1、浅克隆: 只拷贝一层对象的属性,当一个属性的值为基本类型值时,就会正常拷贝,且改变新对象,不会改变原对象中的这个值;当一个属性的值为引用类型值时,拷贝的就是地址,当改变这其中值的时候,也同时会改变原对象中的值。

2、深克隆: 深拷贝则不同,它是将对象中的每一层都拷贝到新对象上,每个属性都指向了不同的地址,这样,无论怎么修改新对象,都不会改变原对象的值

实现深浅克隆

实现浅克隆

  • 展开运算符(…)
var arr = [{name: '数组', action: '浅克隆', obj: {name: "对象"}}, 12];

var brr = [...arr];

console.log(arr, brr);  // [{name: '数组', action: '浅克隆', obj: {name: "对象"}}, 12] [{name: '数组', action: '浅克隆', obj: {name: "对象"}}, 12]

console.log(arr === brr);  // false

// 1.改变数组中第二项的值,这个值为基本类型值
brr[1] = '改变'

console.log(arr, brr);  // [{...}, 12]  [{...}, '改变'2]

// 2.改变数组中第一项中的name值,数组第一项为引用类型值
brr[0].name = "对象";

console.log(arr, brr);  // [{name: '对象', ...}, 12] [{name: '对象', ...}, 12]
  • Object.assign()
var arr = [{name'数组'action'浅克隆'obj: {name"对象"}}, 12];

var brr = Object.assign([], arr)

// 1.改变数组中第二项的值,这个值为基本类型值
brr[1] = '改变'

console.log(arr, brr);  // [{...}, 1, 2]  [{...}, '改变', 2]

// 2.改变数组中第一项中的name值,数组第一项为引用类型值
brr[0].name = "对象";

console.log(arr, brr);  // [{name: '对象', ...}, 1, 2] [{name: '对象', ...}, 1, 2]
  • Array.slice()
var arr = [{name'数组'action'浅克隆'obj: {name"对象"}}, 12];

var brr = arr.slice(0)

console.log(arr, brr); // [{...}, 1, 2] [{...}, 1, 2]

// 1.改变数组中第二项的值,这个值为基本类型值
brr[1] = 5;

console.log(arr, brr); // [{...}, 1, 2] [{...}, 5, 2]

// 2.改变数组中第一项中的name值,数组第一项为引用类型值
brr[0].name = "对象";

console.log(arr, brr);  // [{name: '对象', ...}, 1, 2] [{name: '对象', ...}, 1, 2]
  • Array.from()
var arr = [{name'数组'action'浅克隆'obj: {name"对象"}}, 12];

var brr = Array.from(arr);

console.log(arr, brr); // [{...}, 1, 2] [{...}, 1, 2]

// 1.改变数组中第二项的值,这个值为基本类型值
brr[1] = 5;

console.log(arr, brr); // [{...}, 1, 2] [{...}, 5, 2]

// 2.改变数组中第一项中的name值,数组第一项为引用类型值
brr[0].name = "对象";

console.log(arr, brr);  // [{name: '对象', ...}, 1, 2] [{name: '对象', ...}, 1, 2]

实现深克隆

  • JSON.stringify()和JSON.parse()组合
var arr = [{name'数组'action'浅克隆'obj: {name"对象"}}, 12];

var brr = JSON.parse(JSON.stringify(arr));

console.log(arr, brr); // [{...}, 1, 2] [{...}, 1, 2]

brr[0].name = '对象';

console.log(arr, brr); // [{name: '数组', ...}, 1, 2] [{name: '对象', ...}, 1, 2]

上面我们实现了深克隆,但是这种方法有一个弊端,它在克隆过程中会忽略正则、日期、Symbol、undefined和循环引用对象等

var json = {name'数组'reg/^[0-9]$/}

console.log(JSON.parse(JSON.stringify(json)));  // { name: "数组", reg: {} }

看到结果,你会发现,正则表达式在深克隆之后变成了空对象,所以这个方法是有一定的弊端的,下面,我们自己来写一个深克隆方法~

  • 自己实现

1、第一种方法

// 判断类型函数
function _type(data{
    return Object.prototype.toString.call(data);
}
// 深克隆
function _deepClone(obj{
    // 判断传入数据类型
    if(obj === nullreturn null;

    if(typeof obj !== "object"return obj;

    if(_type(obj) === "[object RegExp]"return new RegExp(obj);

    if(_type(obj) === "[object Date]"return new Date(obj);
    // 创建对象
    let newObj = new obj.constructor;

    for(let key in obj) {
        if(!obj.hasOwnProperty(key)) break;

        // 只要是私有属性就递归调用
        newObj[key] = _deepClone(obj[key])
    }
    return newObj
}

// 测试
var json = {name'数组'reg/^[0-9]$/}

var newJson = _deepClone(json);
console.log(json, newJson)  // { name: "数组", reg: /^[0-9]$/ }  { name: "数组", reg: /^[0-9]$/ }

newJson.name = '对象';

console.log(json, newJson); // { name: "数组", reg: /^[0-9]$/ } { name: "对象", reg: /^[0-9]$/ }

2、第二种方法

function _type(data{
    return Object.prototype.toString.call(data)
}

function _deepClone(obj{
    let newObj = new obj.constructor;
    for(let key in obj) {
        let item = obj[key],
            itemType = _type(obj[key]);
        if(!obj.hasOwnProperty(key)) break;
        // 只有当前属性对应值不是nullq且为“object”时,才递归
        if(obj !== null && typeof item === "object") {
            // 如果值为正则,日期类型,需要特殊处理
            if(/(RegExp|Date)/.test(itemType)) {
                newObj[key] =  new obj.constructor(item);
                continue;
           }
           newObj[key] = _deepClone(item);
           continue;
        }
        newObj[key] = item;
    }
    return newObj;
}

// 测试
var json = {name'数组'reg/^[0-9]$/uundefinedsSymbol('111')}

var newJson = _deepClone(json);
console.log(json, newJson)  // { name: "数组", reg: /^[0-9]$/, u: undefined, s: Symbol(111) }  { name: "数组", reg: /^[0-9]$/, u: undefined, s: Symbol(111) }

newJson.name = '对象';

console.log(json, newJson); // { name: "数组", reg: /^[0-9]$/, u: undefined, s: Symbol(111) } { name: "对象", reg: /^[0-9]$/, u: undefined, s: Symbol(111) }

面试官问:你了解深浅克隆吗?

相信大家在面试中都被问过这个问题,那么你答得怎么样?我们可以从以下几个角度按照顺序回答这个问题:

  • 深浅克隆的定义

  • 深浅克隆的区别

  • 深浅克隆如何实现

  • 深浅克隆在工作中的应用

如果大家有更好的想法回答这个问题,欢迎在评论区交流~

写在最后

最后,欢迎大家关注我的公众号「web前端日记」,关注有福利领取


评论
 上一篇
【React】深入理解虚拟dom和diff算法 【React】深入理解虚拟dom和diff算法
本篇文章同步发表在我的个人博客中:https://zhengyq.club 写在前面 在React中,Virtual Dom和diff的结合大大提高了渲染效率。diff算法由最初的O(n^3)复杂度变为了现在的O(n),那么在这其中
2020-05-21
下一篇 
【redux】从入门到手写实现redux 【redux】从入门到手写实现redux
写在前面 关于redux,我们总是会觉得很难理解,但是当我们沉下心来,用心去理解,会发现也没有那么的困难。下面,这篇文章,将学习redux,带你能够从入门到手写实现redux~ 入门 本文我们将使用react和redux搭配完成 r
2020-04-30