成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

「前端面試題系列9」淺拷貝與深拷貝的含義、區(qū)別及實(shí)現(xiàn)(文末有崗位內(nèi)推哦~)

caige / 2124人閱讀

摘要:深拷貝與淺拷貝的出現(xiàn),就與這兩個(gè)數(shù)據(jù)類型有關(guān)。這時(shí),就需要用淺拷貝來實(shí)現(xiàn)了。數(shù)據(jù)一但過多,就會(huì)有遞歸爆棧的風(fēng)險(xiǎn)。這個(gè)方法是在解決遞歸爆棧問題的基礎(chǔ)上,加以改進(jìn)解決循環(huán)引用的問題。但如果你并不想保持引用,那就改用用于解決遞歸爆棧即可。

前言

這是前端面試題系列的第 9 篇,你可能錯(cuò)過了前面的篇章,可以在這里找到:

數(shù)組去重(10 種濃縮版)

JavaScript 中的事件機(jī)制(從原生到框架)

理解函數(shù)的柯里化

ES6 中箭頭函數(shù)的用法

this 的原理以及用法

偽類與偽元素的區(qū)別及實(shí)戰(zhàn)

如何實(shí)現(xiàn)一個(gè)圣杯布局?

今日頭條 面試題和思路解析

面試的時(shí)候,我經(jīng)常會(huì)問候選人深拷貝與淺拷貝的問題。因?yàn)樗梢钥疾煲粋€(gè)人的很多方面,比如基本功,邏輯能力,編碼能力等等。

另外在實(shí)際工作中,也常會(huì)遇到它。比如用于頁面展示的數(shù)據(jù)狀態(tài),與需要傳給后端的數(shù)據(jù)包中,有部分字段的值不一致的話,就需要在傳參時(shí)根據(jù)接口文檔覆寫那幾個(gè)字段的值。

最常見的可能就是 status 這個(gè)參數(shù)了。界面上的展示需要 Boolean 值,而后端同學(xué)希望拿到的是 Number 值,1 或者 0。為了不影響展示效果,往往就需要深拷貝一下,再進(jìn)行覆寫,否則界面上就會(huì)因?yàn)槟承┲档淖兓霈F(xiàn)奇怪的現(xiàn)象。

至于為什么會(huì)這樣,下文會(huì)講到。馬上開始今天的主題,讓我們先從賦值開始說起。

賦值

Javascript 的原始數(shù)據(jù)類型有這幾種:Boolean、Null、Undefined、Number、String、Symbol(ES6)。它們的賦值很簡(jiǎn)單,且賦值后兩個(gè)變量互不影響。

let test1 = "chao";
let test2 = test1;

// test2: chao

test1 = "chao_change";

// test2: chao
// test1: chao_change

另外的引用數(shù)據(jù)類型有:ObjectArray。深拷貝與淺拷貝的出現(xiàn),就與這兩個(gè)數(shù)據(jù)類型有關(guān)。

const obj = {a:1, b:2};
const obj2 = obj;
obj2.a = 3;
console.log(obj.a); // 3

依照賦值的思路,對(duì) Object 引用類型進(jìn)行拷貝,就會(huì)出問題。很多情況下,這不是我們想要的。這時(shí),就需要用淺拷貝來實(shí)現(xiàn)了。

淺拷貝

什么是淺拷貝?可以這么理解:創(chuàng)建一個(gè)新的對(duì)象,把原有的對(duì)象屬性值,完整地拷貝過來。其中包括了原始類型的值,還有引用類型的內(nèi)存地址。

讓我們用 Object.assign 來改寫一下上面的例子:

const obj = {a:1, b:2};
const obj2 = Object.assign({}, obj);
obj2.a = 3;
console.log(obj.a); // 1

Ok,改變了 obj2 的 a 屬性,但 obj 的 a 并沒有發(fā)生變化,這正是我們想要的。

可是,這樣的拷貝還有瑕疵,再改一下例子:

const arr = [{a:1,b:2}, {a:3,b:4}];
const newArr = [].concat(arr);

newArr.length = 1; // 為了方便區(qū)分,只保留新數(shù)組的第一個(gè)元素
console.log(newArr); // [{a:1,b:2}]
console.log(arr); // [{a:1,b:2},{a:3,b:4}]

newArr[0].a = 123; // 修改 newArr 中第一個(gè)元素的a
console.log(arr[0]); // {a: 123, b: 2},竟然把 arr 的第一個(gè)元素的 a 也改了

oh,no!這不是我們想要的...

經(jīng)過一番查找,才發(fā)現(xiàn):原來,對(duì)象的 Object.assign(),數(shù)組的 Array.prototype.slice()Array.prototype.concat(),還有 ES6 的 擴(kuò)展運(yùn)算符,都有類似的問題,它們都屬于 淺拷貝。這一點(diǎn),在實(shí)際工作中處理數(shù)據(jù)的組裝時(shí),要格外注意。

所以,我將淺拷貝這樣定義:只拷貝第一層的原始類型值,和第一層的引用類型地址。

深拷貝

我們當(dāng)然希望當(dāng)拷貝多層級(jí)的對(duì)象時(shí),也能實(shí)現(xiàn)互不影響的效果。所以,深拷貝的概念也就油然而生了。我將深拷貝定義為:拷貝所有的屬性值,以及屬性地址指向的值的內(nèi)存空間。

也就是說,當(dāng)遇到對(duì)象時(shí),就再新開一個(gè)對(duì)象,然后將第二層源對(duì)象的屬性值,完整地拷貝到這個(gè)新開的對(duì)象中。

按照淺拷貝的思路,很容易就想到了遞歸調(diào)用。所以,就自己封裝了個(gè)深拷貝的方法:

function deepClone(obj) {
    if(!obj && typeof obj !== "object"){
        return;
    }
    var newObj= toString.call(obj) === "[object Array]" ? [] : {};
    for (var key in obj) {
        if (obj[key] && typeof obj[key] === "object") {
            newObj[key] = deepClone(obj[key]);
        } else {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

再試試看:

let arr = [{a:1,b:2}, {a:3,b:4}];
let newArr = deepClone(arr);

newArr.length = 1; // 為了方便區(qū)分,只保留新數(shù)組的第一個(gè)元素
console.log(newArr); // [{a:1, b:2}]
console.log(arr); // [{a:1, b:2}, {a:3, b:4}]

newArr[0].a = 123; // 修改 newArr 中第一個(gè)元素的 a
console.log(arr[0]); // {a:1, b:2}

ok,這下搞定了。

不過,這個(gè)方法貌似會(huì)存在 引用丟失 的的問題。比如這樣:

var b = {};
var a = {a1: b, a2: b};

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

var c = clone(a);
c.a1 === c.a2 // false

如果我們的需求是,應(yīng)該丟失引用,那就可以用這個(gè)方法。反之,就得想辦法解決。

一行代碼的深拷貝

當(dāng)然,還有最簡(jiǎn)單粗暴的深拷貝方法,就是利用 JSON 了。像這樣:

let newArr2 = JSON.parse(JSON.stringify(arr));
console.log(arr[0]); // {a:1, b:2}
newArr2[0].a = 123;
console.log(arr[0]); // {a:1, b:2}

但是,JSON 內(nèi)部用了遞歸的方式。數(shù)據(jù)一但過多,就會(huì)有遞歸爆棧的風(fēng)險(xiǎn)。

// Maximum call stack size exceeded
深拷貝的終極方案

有位大佬給出了深拷貝的終極方案,利用了“?!钡乃枷?。

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

    let root = {};

    // 循環(huán)數(shù)組
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];

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

        // 初始化賦值目標(biāo),key為undefined則拷貝到父元素,否則拷貝到子元素
        let res = parent;
        if (typeof key !== "undefined") {
            res = parent[key] = {};
        }

        // 數(shù)據(jù)已經(jīng)存在
        let uniqueData = uniqueList.find((item) => item.source === data );
        if (uniqueData) {
            parent[key] = uniqueData.target;
            // 中斷本次循環(huán)
            continue;
        }

        // 數(shù)據(jù)不存在
        // 保存源數(shù)據(jù),在拷貝數(shù)據(jù)中對(duì)應(yīng)的引用
        uniqueList.push({
            source: data,
            target: res,
        });

        for(let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === "object") {
                    // 下一次循環(huán)
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

其思路是:引入一個(gè)數(shù)組 uniqueList 用來存儲(chǔ)已經(jīng)拷貝的數(shù)組,每次循環(huán)遍歷時(shí),先判斷對(duì)象是否在 uniqueList 中了,如果在的話就不執(zhí)行拷貝邏輯了。

這個(gè)方法是在解決遞歸爆棧問題的基礎(chǔ)上,加以改進(jìn)解決循環(huán)引用的問題。但如果你并不想保持引用,那就改用 cloneLoop(用于解決遞歸爆棧)即可。有興趣的同學(xué),可以前往 深拷貝的終極探索(90%的人都不知道),查看更多的細(xì)節(jié)。

總結(jié)

所謂深拷貝與淺拷貝,指的是 ObjectArray 這樣的引用數(shù)據(jù)類型。

淺拷貝,只拷貝第一層的原始類型值,和第一層的引用類型地址。

深拷貝,拷貝所有的屬性值,以及屬性地址指向的值的內(nèi)存空間。通過遞歸調(diào)用,或者 JSON 來做深拷貝,都會(huì)有一些問題。而 cloneForce 方法倒是目前看來最完美的解決方案了。

在日常的工作中,我們要特別注意,對(duì)象的 Object.assign(),數(shù)組的 Array.prototype.slice()Array.prototype.concat(),還有 ES6 的 擴(kuò)展運(yùn)算符,都屬于淺拷貝。當(dāng)需要做數(shù)據(jù)組裝時(shí),一定要用深拷貝,以免影響界面展示效果。

崗位內(nèi)推

莉莉絲游戲招 中高級(jí)前端工程師 啦?。。?/p>

你玩過《小冰冰傳奇([刀塔傳奇])》么?你玩過《劍與家園》么?

你想和 薛兆豐老師 成為同事么?有興趣的同學(xué),可以 關(guān)注下面的公眾 號(hào)加我微信 詳聊哈~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103559.html

相關(guān)文章

  • 「讀懂源碼系列3」lodash 是如何實(shí)現(xiàn)拷貝(上)

    摘要:上對(duì)位運(yùn)算的解釋是它經(jīng)常被用來創(chuàng)建處理以及讀取標(biāo)志位序列一種類似二進(jìn)制的變量。位運(yùn)算,常用于處理同時(shí)存在多個(gè)布爾選項(xiàng)的情形。掩碼中的每個(gè)選項(xiàng)的值都是的冪,位運(yùn)算是位的。位運(yùn)算,說白了就是直接對(duì)某個(gè)數(shù)據(jù)在內(nèi)存中的二進(jìn)制位,進(jìn)行運(yùn)算操作。 showImg(https://segmentfault.com/img/bVbrC56?w=2208&h=1242); 前言 上一篇文章 「前端面試題...

    flyer_dev 評(píng)論0 收藏0
  • 前端面試 -- JavaScript (一)

    摘要:前言前兩天總結(jié)了一下方面的面試題傳送門,今天翻看了一些面試中常見的幾個(gè)問題只是一部分,會(huì)持續(xù)更新,分享給有需要的小伙伴,歡迎關(guān)注如果文章中有出現(xiàn)紕漏錯(cuò)誤之處,還請(qǐng)看到的小伙伴留言指正,先行謝過以下有哪些數(shù)據(jù)類型種原始數(shù)據(jù)類型布爾表示一個(gè)邏輯 前言 前兩天總結(jié)了一下HTML+CSS方面的面試題 (傳送門),今天翻看了一些 JavaScript 面試中常見的幾個(gè)問題(只是一部分,會(huì)持續(xù)更新...

    junnplus 評(píng)論0 收藏0
  • 2018大廠高級(jí)前端面試匯總

    摘要:面試的公司分別是阿里網(wǎng)易滴滴今日頭條有贊挖財(cái)滬江餓了么攜程喜馬拉雅兌吧微醫(yī)寺庫寶寶樹??低暷⒐浇挚峒覙钒俜贮c(diǎn)和海風(fēng)教育。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本人于7-8月開始準(zhǔn)備面試,過五關(guān)斬六將,最終抱得網(wǎng)易歸,深深感受到高級(jí)前端面試的套路。以下是自己整理的面試題匯總,不敢藏私,統(tǒng)統(tǒng)貢獻(xiàn)出來。 面試的公司分...

    zzir 評(píng)論0 收藏0
  • 「讀懂源碼系列4」lodash 是如何實(shí)現(xiàn)拷貝(下)

    摘要:用于檢測(cè)自己是否在自己的原型鏈上如果是函數(shù),則取出該函數(shù)的原型對(duì)象否則,取出對(duì)象的原型對(duì)象其中,的判斷,是為了確定的類型是對(duì)象或數(shù)組。相當(dāng)于,而的構(gòu)造函數(shù)是一個(gè)函數(shù)對(duì)象。 showImg(https://segmentfault.com/img/bVbq2N1?w=640&h=437); 前言 接著上一篇文章 lodash 是如何實(shí)現(xiàn)深拷貝的(上),今天會(huì)繼續(xù)解讀 _.cloneDee...

    zombieda 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<