摘要:基本數據類型將變量和值一起放在棧內存引用數據類型則將變量放在棧內存而將值放在堆內存。該怎么理解沒圖我說個假設有個變量在內存中是這樣的棧內存中的變量指向堆內存中一塊內存相當于持有該內存的指針,而那塊內存中存儲變量的相關內容。
趁著周五沒那么忙,抽個空整理一下最近使用比較頻繁的一個小技術 對象的深復制。
感覺啊,這個標題和今天的節日(假裝不知道原來是情人節)那么遙相呼應。啊,沒有女朋友?沒有女朋友沒關系,復制一個啊。先走你一個:console.log("GirlFriend")。
同時筆者需要預個警,本篇注重實際中的應用情況,中規中矩,不搞花里胡哨。所以主要是對數組和字面量對象進行討論,畢竟前端開發中遇到最多的對象就是這兩個了。在正式分析對象的深復制這個問題之前,筆者覺得有必要對在座的朋友科普下必要的知識。
JavaScript中數據類型分為哪幾種?
這個問題在面試中的命中率真的挺高,而且也是本篇的重要所在。了解的朋友可跳過繼續向下看,不了解的朋友請跟著我慢慢理解。
JavaScript中數據類型分為基本數據類型和引用數據類型 (當然還有別的叫法,只是筆者認為這種叫法更科學)。
基本數據類型: null, undefined, int, string, boolean引用數據類型: {"a":"b"}(字面量對象), Array, Map, Set, Symbol 等等...其實就是基本數據類型以外的類型
而且,不同的數據類型在內存中的存儲方式也是不同的:
基本數據類型在內存中的存儲方式我們都知道,JavaScript中的內存分為棧內存和堆內存(不知道?請點擊)。基本數據類型將變量和值一起放在棧內存;引用數據類型則將變量放在棧內存而將值放在堆內存。先來講基本數據類型。
基本數據類型在JavaScript編程中用得極為廣泛,比如:
let a = 1; let b = "name"; let c = true; let d = null; let e = undefined;
這些都屬于基本數據類型,都存儲在棧內存中;如以下形式:
棧內存中的變量數據有個好處是如果需要獲取某個變量值的copy值很方便,直接通過 = 操作即可,不需要考慮額外的問題。比如:let f = a; 這個操作意在拷貝一份 a 這個變量,此時棧內存中就變成這樣了:
多出一個變量 f,即使說是從 a 復制而來的,結果卻和 a 沒有任何關系。用代碼驗證一下:
let a = 1; let f = a; //此時改變a的值 a = 2; console.log(`a 是${a}`); console.log(`f 是${f}`);
運行結果:
上面說到:引用數據類型則將變量放在棧內存而將值放在堆內存。該怎么理解?沒圖我說個jb?
假設有個變量person:
let person={ "name":"Mario" }
在內存中是這樣的
棧內存中的變量 person 指向堆內存中一塊內存(相當于持有該內存的指針),而那塊內存中存儲 person 變量的相關內容。
因此可以看出引用數據類型的復制并沒有基本數據類型來得方便。即便如此,我們還是來證實下這個想法:
let person={ "name":"Mario" } let person_copy = person; //修改person中的內容 person["name"]="JavaScript"; console.log(`person: ${person["name"]}`); console.log(`person_copy: ${person_copy["name"]}`);
運行結果:
可以看出當我們復制好一份 person_copy ,并對 person 進行了一次修改。結果兩個變量同時變化。為什么?
因為當程序進行到let person_copy = person;這里的時候,并不是直接把 person 的內容賦值給變量 person_copy,而是把 person 的指針賦值給了變量 person_copy。
所以說變量 person 和 person_copy 都持有了該內存塊的指針,因此不管哪個變量修改了內存中的內容,另一個變量對應的值也會變化。最終我們可以確定:如果想復制一個引用類型的數據,就是要將該內存塊中的內容復制進另一個內存塊中并把新的指針賦值給新變量。
大概內容已經科普完了,接下來就開始本文的重點內容
對象的(深)復制在開發過程中,如果某條數據(尤其是從后臺請求回來的數據)使用頻率很高而且用途復雜,那么就不得不為它進行一次復制以備不時之需。針對使用頻率較高的兩個對象數組和字面量對象,我們開始逐一討論。
數組的復制數組在實際開發中,元素主要分為基本數據類型和字面量對象(不排除還有別的類型數據,只是筆者沒遇到過,所以只針對普遍的情況)。
針對元素是基本數據類型的數組的復制操作,筆者提供4種方法:
ES6的對象擴展運算符 [...]
let origin = [1, 2, 3, 4, 5]; let another = [...origin]; //向原數組中添加一個元素 origin.push(6); console.log(`another元素: ${another}`);
運算結果:
slice
let origin = [1, 2, 3, 4, 5]; let another = origin.slice(); //向原數組中添加一個元素 origin.push(6); console.log(`another元素: ${another}`);
運行結果:
concat
let origin = [1, 2, 3, 4, 5]; //相當于向origin中拼接一個空數組 let another = origin.concat([]); //向原數組中添加一個元素 origin.push(6); console.log(`another元素: ${another}`);
運行結果:
JSON.stringify
這個方法可行但是幾乎沒人這么用。原理是將數組轉為字符串再轉回數組類型。
let origin = [1, 2, 3, 4, 5]; let another = JSON.parse(JSON.stringify(origin)); //向原數組中添加一個元素 origin.push(6); console.log(`another元素: ${another}`);
運行結果:
其中筆者覺得第一和第二個方法比較好用。不過如果數組中的元素是字面量對象的話,請繼續向下看。
字面量對象的深復制實際開發中,所謂字面量對象可以人為是一段json數據。json的重要性不用說,那么相當重要,前后端交互的核心。
先給出一段json:
{ "name": "Mario", "age": 26, "isCoder": true, "homeWebPage": null, "fullStackSkills": undefined, "hobbies": ["LoL", "Travel", "Coding"], "phone": { "home": 123321, "office": 456654 } }
首先分析這段json中的數據,不管是基本數據類型還是引用數據類型都有了,所以想要復制這個json對象,我們需要針對不同的數據做不同的處理。根據科普的知識我們已經知道,復制基本數據類型可以直接賦值,對于引用數據類型則不行,而且主要使用到的數組和字面量對象(這里也可以認為是json)這兩個類型數據的復制方法都不同。因此我們首先要判斷某一個數值是否是對象:
//判斷item是否為"object";該方法主要是為了區分參數是基本類型還是引用類型 function isObject(item) { return (item === null || item === undefined) ? false : (typeof item === "object"); }
其次,我們可以看到origin是一段json,orgin中的phone字段對應的值也是一段json,所以要想整個復制這個對象難免要用到遞歸,一層一層嵌套進行。奉上核心代碼:
/** * * @param {字面量對象} origin * @param {origin的鏡像對象} mirror */ function deepClone(origin, mirror) { //獲取該字面量對象的所有的key let keys = Object.keys(origin); //遍歷所有的key已保證復制的完整 keys.forEach(key => { let value = origin[key]; if (isObject(value)) { //判斷是否為對象,如果是則需要額外處理;如果不是則直接復制 if (Array.isArray(value)) { //判斷是否為數組,如果是則需要復制該數組并存入mirror;如果不是則進行遞歸調用 let copy = value.slice(); mirror[key] = copy; } else { //初始化本次字面量對象的鏡像對象 let obj = {}; mirror[key] = obj; //引用傳值 //遞歸調用 deepClone(value, obj); } } else { mirror[key] = value; } }); }
通過測試,
//Test let mirror = {}; deepClone(origin, mirror); //向原對象中的hobbies中增加一項 origin["hobbies"].push("Eat"); console.log(mirror);
運行結果:
證明方法有效。
當然針對json數據的復制,也可以只用JSON.parse(JSON.stringify(origin))實現,具體效率怎么樣,筆者也沒有進行測試。所以這塊有待驗證。因為該文章注重實際開發中的應用,所以例子沒有用到復雜的對象(例如:Set, Symbol 等等...)。所以如果有這個疑問的朋友也不用糾結了。
寫得差不多了,能想到了就這些了...收拾收拾準備跑路了
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108309.html
摘要:當發生網絡分區時,你將面臨兩個選擇如果堅持保持各節點之間的數據一致性選擇,你需要等待網絡分區恢復后,將數據復制完成,才可以向外部提供服務。期間發生網絡分區將不能對外提供服務,因為它保證不了數據一致性。則強調是高可用,對數據一致性要求更低。這篇文章著重點不在于科普,畢竟關于CAP、BASE的理論的文章,網上很多。所以本文科普篇幅盡量小(只包含概念描述)。主要從幾個側面的問題來描述CAP,進而描...
摘要:部分是對前兩部分的簽名,防止數據篡改。也就是說,一旦簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。為了減少盜用,的有效期應該設置得比較短。為了減少盜用,不應該使用協議明碼傳輸,要使用協議傳輸。 JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案,本文介紹它的原理和用法。 showImg(https://www.wangbase.com/blogimg...
摘要:而,是部分內容的緩存,智能程度更高。用戶向緩存服務器發起請求,緩存服務器響應用戶請求,將用戶所需內容傳送到用戶終端。內容進行分發后,源服務器的被隱藏,受到攻擊的概率會大幅下降。由一個核心云計算中心,對所有終端節點提供服務。如今這個移動互聯網時代,越來越多的人使用手機觀看視頻,豐富自己的娛樂生活。可是,大家在追劇的時候,有沒有想過一個問題——為什么有時候明明自己的網速很快,但觀看視頻時,仍然卡...
摘要:樣例前端傳入字段和結構。后臺按照前端的需求返回數據。則將前后臺通信直接分為兩大類和。顧名思義,是默認的操作符,代表查詢,是不會給服務端帶來副作用的請求。文檔文檔部分文檔就是前端向后臺描述所需的字段。降低前后端溝通成本。 簡介 showImg(https://segmentfault.com/img/bVbmKX5?w=150&h=150); GraphQL是基于「類型系統」來執行查詢的...
摘要:例如這說明在應用程序的消息中響應。這意味著應用程序從中提取信息對其進行處理,并顯示給用戶。配置使用服務器。根據請求,服務器建立一個通道與受害人進行交互。受害者受害者部分顯示了受害者的名單。 理解xss shell是什么之前,讓我們一起回顧一下一些基本的xss(跨站腳本),xss是最常見的一個漏洞,存在于今天許多的web應用程序。xss是攻擊者試圖通過web應用程序執行惡意腳本的技術,攻...
閱讀 1711·2021-11-22 12:09
閱讀 1451·2019-08-30 13:22
閱讀 2083·2019-08-29 17:00
閱讀 2635·2019-08-29 16:28
閱讀 2945·2019-08-26 13:51
閱讀 1174·2019-08-26 13:25
閱讀 3238·2019-08-26 12:14
閱讀 3006·2019-08-26 12:14