9012年,當咱們討論js深淺拷貝時咱們在說些什麼?

前言:

本文主要閱讀對象:對深淺拷貝印象模糊對初級前端,想對js深淺拷貝聊一聊的中級前端。
 若是是對這些有完整對認知體系和解決方法的大佬,能夠選擇略過。
複製代碼

正文: 討論深淺拷貝,首先要從js的基本數據類型提及: 根據 JavaScript 中的變量類型傳遞方式,分爲值類型和引用類型, 值類型變量包括 Boolean、String、Number、Undefined、Null。
引用類型包括了 Object 類的全部, 如 Date、Array、Function 等。在參數傳遞方式上,值類型是按值傳遞,引用類型是按地址傳遞。前端

咱們來看一下這兩則有什麼區別:node

//eg1:
// 值類型
var a = 10
var b = a
b = 20
console.log(a)  // 10
console.log(b)  // 20

//解析:上述代碼中,a b都是值類型,二者分別修改賦值,相互之間沒有任何影響。再看引用類型的例子:

//eg2
// 引用類型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a)  // {x: 100, y: 200}
console.log(b)  // {x: 100, y: 200}
複製代碼

解析: 上述代碼中,a b都是引用類型。在執行了b = a以後,修改b的屬性值,a的也跟着變化。由於a和b都是引用類型,指向了同一個內存地址,即二者引用的是同一個值,所以b修改屬性時,a的值隨之改動。數組

**那到底什麼是深淺拷貝呢?**
<br />解析:深淺拷貝是拷貝對象的`深度`來講的:
<br />當你想拷貝的對象只有一級時,就是淺拷貝。
<br />當你拷貝的對象有多級的時候,就是深拷貝。
複製代碼

再回到上面的那個例子: 在例子能看出來,若是直接採用「=」賦值,這種類型,當咱們改變b的值時候,a也會隨之改變的,假如不容許改變a呢? 這時,深拷貝就出場了。bash

function clone(val){
    if(!val && typeof val !== 'object'){
        return
    }

    const newArr = toString.call(val) === ['object Array'] ? [] : {}

    for (let item in val) {
        if(typeof val[item] ===  'object') {
            newArr[item] = clone(item)
        }else {
            newArr[item] = val[item]
        }
    }
    return newArr
 }

//測試:
    var a = {x: 10, y: 20}
    var b = clone(a)
    b.x = 100
    b.y = 200
    console.log(a)  // {x: 10, y: 20}
    console.log(b)  //{x: 100, y: 200}

複製代碼

解析: 對於這個深拷貝,你們看到這裏應該均可以看明白:主要思路就是淺拷貝 + 遞歸。
這有幾個點須要注意:
1. 判斷接收到的參數類型。
2. 使用toString.call()判斷更加嚴謹。
3. 考慮對其餘引用類型的數據兼容(這裏並無對每種類型的數據組作判斷,引用類型包括了 Object 類的全部,如 Date、Array、Function 等。)微信

須要注意的是:用遞歸的話,會有一下幾個須要特別注意的點:
    1. 當數據層次很深時,容易爆棧(棧溢出) 解決辦法=>
        a. 消除尾遞歸
        b. 改用循環

    2. 「循環引用」,會致使死循環  解決辦法 =>
        a. 循環檢測
        b. 暴力破解

第一種:數據廣度特別大,層次特別深
第二種:相似下面的這種狀況
    var a = {}
    a.a = a
    clone(a) //死循環
    

ES6中一行代碼實現深拷貝:JSON.parse(JSON.stringify(source))
註釋:JSON.stringify()其實利用了「循環檢測」機制
複製代碼

這裏給你們推薦一篇循環解決遞歸的方法: 能夠保持拷貝數據之後的引用關係oop

function cloneForce(x) {
    const uniqueList = []; // 用來去重

    let root = {};

    // 循環數組
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];

    while(loopList.length) {
        // 深度優先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化賦值目標,key爲undefined則拷貝到父元素,不然拷貝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }
        
        // 數據已經存在
        let uniqueData = find(uniqueList, data);
        if (uniqueData) {
            parent[key] = uniqueData.target;
            continue; // 中斷本次循環
        }

        // 數據不存在
        // 保存源數據,在拷貝數據中對應的引用
        uniqueList.push({
            source: data,
            target: res,
        });
    
        for(let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循環
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

function find(arr, item) {
    for(let i = 0; i < arr.length; i++) {
        if (arr[i].source === item) {
            return arr[i];
        }
    }

    return null;
}

//eg1:
var a = {
    a1: b,
    a2: b,
}

var b = {};

a.a1 === a.a2 // true

var c = cloneForce(a);
c.a1 === c.a2 // true   引用保持一致

//eg2:
var a = {};
a.a = a;

console.log(cloneForce(a))//還能夠破解循環引用
複製代碼

主要思路是: 聲明一個數組對象(父對象,key,value),其有值時,取其最後一個對象; 判斷key有值,表明當前級下有子級,則拷貝到子元素。若是數據已經存在,則中斷本次循環。數據不存在則對其拷貝。
有一點須要注意:cloneForce在對象數量不少時會出現很大的問題,若是數據量很大不適合使用cloneForce。post

有興趣的同窗,能夠前往 深拷貝的終極探索(90%的人都不知道),查看更多的細節。測試

總結:
若是以爲對你有幫助,請給做者一點小小的鼓勵, 點個贊或者收藏吧。 有須要溝通的請聯繫我: 微信( wx9456d )郵箱( allan_liu986@163.com )ui

相關文章
相關標籤/搜索