php深淺拷貝,JavaScript 中的深淺拷貝

工作中經常會遇到需要復制 JavaScript 數據的時候,遇到 bug 時實在令人頭疼;面試中也經常會被問到如何實現一個數據的深淺拷貝,但是你對其中的原理清晰嗎?一起來看一下吧!

一、為什么會有深淺拷貝

想要更加透徹的理解為什么 JavaScript 會有深淺拷貝,需要先了解下 JavaScript 的數據類型有哪些,一般分為基本類型(Number、String、Null、Undefined、Boolean、Symbol )和引用類型(對象、數組、函數)。

基本類型是不可變的,任何方法都無法改變一個基本類型的值,也不可以給基本類型添加屬性或者方法。但是可以為引用類型添加屬性和方法,也可以刪除其屬性和方法。

基本類型和引用類型在內存中的存儲方式也大不相同,基本類型保存在棧內存中,而引用類型保存在堆內存中。為什么要分兩種保存方式呢? 因為保存在棧內存的必須是大小固定的數據,引用類型的大小不固定,只能保存在堆內存中,但是我們可以把它的地址寫在棧內存中以供我們訪問。

說來這么多,我們來看個示例:

let num1 = 10;

let obj1 = {

name: "hh"

}

let num2 = num1;

let obj2 = obj1;

num2 = 20;

obj2.name = "kk";

console.log(num1); // 10

console.log(obj1.name); // kk

執行完這段代碼,內存空間里是這樣的:

bVblqXC?w=1112&h=626

可以看到 obj1 和 obj2 都保存了一個指向該對象的指針,所有的操作都是對該引用的操作,所以對 obj2 的修改會影響 obj1。

小結:

之所以會出現深淺拷貝,是由于 JS 對基本類型和引用類型的處理不同。基本類型指的是簡單的數據段,而引用類型指的是一個對象保存在堆內存中的地址,JS 不允許我們直接操作內存中的地址,也就是說不能操作對象的內存空間,所以,我們對對象的操作都只是在操作它的引用而已。

在復制時也是一樣,如果我們復制一個基本類型的值時,會創建一個新值,并把它保存在新的變量的位置上。而如果我們復制一個引用類型時,同樣會把變量中的值復制一份放到新的變量空間里,但此時復制的東西并不是對象本身,而是指向該對象的指針。所以我們復制引用類型后,兩個變量其實指向同一個對象,所以改變其中的一個對象,會影響到另外一個。

二、深淺拷貝

1. 淺拷貝

淺拷貝只是復制基本類型的數據或者指向某個對象的指針,而不是復制對象本身,源對象和目標對象共享同一塊內存;若對目標對象進行修改,存在源對象被篡改的可能。

我們來看下淺拷貝的實現:

/* sourceObj 表示源對象

* 執行完函數,返回目標對象

*/

function shadowClone (sourceObj = {}) {

let targetObj = Array.isArray(sourceObj) ? [] : {};

let copy;

for (var key in sourceObj) {

copy = sourceObj[key];

targetObj[key] = copy;

}

return targetObj;

}

// 定義 source

let sourceObj = {

number: 1,

string: 'source1',

boolean: true,

null: null,

undefined: undefined,

arr: [{name: 'arr1'}, 1],

func: () => 'sourceFunc1',

obj: {

string: 'obj1',

func: () => 'objFunc1'

}

}

// 拷貝sourceObj

let copyObj = shadowClone(sourceObj);

// 修改 sourceObj

copyObj.number = 2;

copyObj.string = 'source2';

copyObj.boolean = false;

copyObj.arr[0].name = 'arr2';

copyObj.func = () => 'sourceFunc2';

copyObj.obj.string = 'obj2';

copyObj.obj.func = () => 'objFunc2';

// 執行

console.log(sourceObj);

/* {

number: 1,

string: 'source1',

boolean: true,

null: null,

undefined: undefined,

arr: [{name: 'arr2'}],

func: () => 'sourceFunc1',

obj: {

func: () => 'objFunc2',

string: 'obj2'

}

}

*/

2. 深拷貝

深拷貝能夠實現真正意義上的對象的拷貝,實現方法就是遞歸調用“淺拷貝”。深拷貝會創造一個一模一樣的對象,其內容地址是自助分配的,拷貝結束之后,內存中的值是完全相同的,但是內存地址是不一樣的,目標對象跟源對象不共享內存,修改任何一方的值,不會對另外一方造成影響。

/* sourceObj 表示源對象

* 執行完函數,返回目標對象

*/

function deepClone (sourceObj = {}) {

let targetObj = Array.isArray(sourceObj) ? [] : {};

let copy;

for (var key in sourceObj) {

copy = sourceObj[key];

if (typeof(copy) === 'object') {

if (copy instanceof Object) {

targetObj[key] = deepClone(copy);

} else {

targetObj[key] = copy;

}

} else if (typeof(copy) === 'function') {

targetObj[key] = eval(copy.toString());

} else {

targetObj[key] = copy;

}

}

return targetObj;

}

// 定義 sourceObj

let sourceObj = {

number: 1,

string: 'source1',

boolean: true,

null: null,

undefined: undefined,

arr: [{name: 'arr1'}],

func: () => 'sourceFunc1',

obj: {

string: 'obj1',

func: () => 'objFunc1'

}

}

// 拷貝sourceObj

let copyObj = deepClone(sourceObj);

// 修改 source

copyObj.number = 2;

copyObj.string = 'source2';

copyObj.boolean = false;

copyObj.arr[0].name = 'arr2';

copyObj.func = () => 'sourceFunc2';

copyObj.obj.string = 'obj2';

copyObj.obj.func = () => 'objFunc2';

// 執行

console.log(sourceObj);

/* {

number: 1,

string: 'source1',

boolean: true,

null: null,

undefined: undefined,

arr: [{name: 'arr1'}],

func: () => 'sourceFunc1',

obj: {

func: () => 'objFunc1',

string: 'obj1'

}

}

*/

兩個方法可以合并在一起:

/* deep 為 true 表示深復制,為 false 表示淺復制

* sourceObj 表示源對象

* 執行完函數,返回目標對象

*/

function clone (deep = true, sourceObj = {}) {

let targetObj = Array.isArray(sourceObj) ? [] : {};

let copy;

for (var key in sourceObj) {

copy = sourceObj[key];

if (deep && typeof(copy) === 'object') {

if (copy instanceof Object) {

targetObj[key] = clone(deep, copy);

} else {

targetObj[key] = copy;

}

} else if (deep && typeof(copy) === 'function') {

targetObj[key] = eval(copy.toString());

} else {

targetObj[key] = copy;

}

}

return targetObj;

}

三、使用技巧

1. concat()、slice()

(1)若拷貝數組是純數據(不含對象),可以通過concat() 和 slice() 來實現深拷貝;

let a = [1, 2];

let b = [3, 4];

let copy = a.concat(b);

a[1] = 5;

b[1] = 6;

console.log(copy);

// [1, 2, 3, 4]

let a = [1, 2];

let copy = a.slice();

copy[0] = 3;

console.log(a);

// [1, 2]

(2)若拷貝數組中有對象,可以使用 concat() 和 slice() 方法來實現數組的淺拷貝。

let a = [1, {name: 'hh1'}];

let b = [2, {name: 'kk1'}];

let copy = a.concat(b);

copy[1].name = 'hh2';

copy[3].name = 'kk2';

console.log(copy);

// [1, {name: 'hh2'}, 2, {name: 'kk2'}]

無論 a[1].name 或者 b[1].name 改變,copy[1].name 的值都會改變。

let a = [1, {name: 'hh1'}];

let copy = a.slice();

copy[1].name = 'hh2';

console.log(a);

// [1, {name: 'hh2'}]

改變了 a[1].name 后,copy[1].name 的值也改變了。

2. Object.assign()、Object.create()

Object.assign()、Object.create() 都是一層(根級)深拷貝,之下的級別為淺拷貝。

(1) 若拷貝對象只有一級,可以通過 Object.assign()、Object.create() 來實現對象的深拷貝;

let sourceObj = {

str: 'hh1',

number: 10

}

let targetObj = Object.assign({}, sourceObj)

targetObj.str = 'hh2'

console.log(sourceObj);

// {str: 'hh1', number: 10}

let sourceObj = {

str: 'hh1',

number: 10

}

let targetObj = Object.create(sourceObj)

targetObj.str = 'hh2'

console.log(sourceObj);

// {str: 'hh1', number: 10}

(2) 若拷貝對象有多級, Object.assign()、Object.create() 實現的是對象的淺拷貝。

let sourceObj = {

str: 'hh',

number: 10,

obj: {

str: 'kk1'

}

}

let targetObj = Object.assign({}, sourceObj)

targetObj.obj.str = 'kk2'

console.log(sourceObj);

// {

// str: 'hh',

// number: 10,

// obj: {

// str: 'kk2'

// }

// }

let sourceObj = {

str: 'hh',

number: 10,

obj: {

str: 'kk1'

}

}

let targetObj = Object.create(sourceObj)

targetObj.obj.str = 'kk2'

console.log(sourceObj);

// {

// str: 'hh',

// number: 10,

// obj: {

// str: 'kk2'

// }

// }

修改了 targetObj.obj.str 的值之后,sourceObj.obj.str 的值也改變了。

3. 對象的解構

對象的解構同 Object.assign() 和 Object.create(),都是一層(根級)深拷貝,之下的級別為淺拷貝。

(1)若拷貝對象只有一層,可以通過對象的解構來實現深拷貝;

let sourceObj = {

str: 'hh1',

number: 10

}

let targetObj = {...sourceObj};

targetObj.str = 'hh2'

console.log(sourceObj);

// {str: 'hh1', number: 10}

(2)若拷貝對象有多層,通過對象的解構實現的是對象的淺拷貝。

let sourceObj = {

str: 'hh',

number: 10,

obj: {

str: 'kk1'

}

}

let targetObj = {...sourceObj};

targetObj.obj.str = 'kk2'

console.log(sourceObj);

// {

// str: 'hh',

// number: 10,

// obj: {

// str: 'kk2'

// }

// }

4. JSON.parse()

用 JSON.stringify() 把對象轉成字符串,再用 JSON.parse() 把字符串轉成新的對象,可以實現對象的深復制。

let source = ['hh', 1, [2, 3], {name: 'kk1'}];

let copy = JSON.parse(JSON.stringify(source));

copy[2][1] = 4;

copy[3].name = 'kk2';

console.log(source);

// ['hh', 1, [2, 3], {name: 'kk1'}]

可以看出,雖然改變了 copy[2].name 的值,但是 source[2].name 的值沒有改變。

JSON.parse(JSON.stringify(obj)) 不僅能復制數組還可以復制對象,但是幾個弊端:

1)它會拋棄對象的 constructor,深拷貝之后,不管這個對象原來的構造函數是什么,在深拷貝之后都會變成 Object;

2)這種方法能正確處理的對象只有?Number, String, Boolean, Array, 扁平對象,即那些能夠被 json 直接表示的數據結構。RegExp 對象是無法通過這種方式深拷貝。

3)只有可以轉成 JSON 格式的對象才可以這樣用,像 function 沒辦法轉成 JSON。

5. 可以使用的庫

以下兩種庫都能實現深淺拷貝,有各自的使用方法。

jQuery

具體使用可以參考:官方文檔

Lodash

具體使用可以參考:官方文檔

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/392289.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/392289.shtml
英文地址,請注明出處:http://en.pswp.cn/news/392289.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用Python進行地理編碼和反向地理編碼

Geocoding is the process of taking input text, such as an address or the name of a place, and returning a latitude/longitude location. To put it simply, Geocoding is converting physical address to latitude and longitude.地理編碼是獲取輸入文本(例如地址或地點…

java開發簡歷編寫_如何通過幾個簡單的步驟編寫出色的初級開發人員簡歷

java開發簡歷編寫So you’ve seen your dream junior developer role advertised, and are thinking about applying. It’s time to write that Resume! Nothing better than sitting down to a blank piece of paper and not knowing how to start, right?因此,您…

leetcode 628. 三個數的最大乘積(排序)

給定一個整型數組,在數組中找出由三個數組成的最大乘積,并輸出這個乘積。 示例 1: 輸入: [1,2,3] 輸出: 6 解題思路 最大的乘積可能有兩種情況 1.兩個最小負數和一個最大正數 2.三個最大正數 代碼 class Solution {public int maximumProduct(int[…

[Object-C語言隨筆之三] 類的創建和實例化以及函數的添加和調用!

上一小節的隨筆寫了常用的打印以及很基礎的數據類型的定義方式,今天就來一起學習下如何創建類與函數的一些隨筆; 首先類的創建:在Xcode下,菜單File-New File,然后出現選擇class模板,如下圖&…

2024-AI人工智能學習-安裝了pip install pydot但是還是報錯

2024-AI人工智能學習-安裝了pip install pydot但是還是報錯 出現這樣子的錯誤: /usr/local/bin/python3.11 /Users/wangyang/PycharmProjects/studyPython/tf_model.py 2023-12-24 22:59:02.238366: I tensorflow/core/platform/cpu_feature_guard.cc:182] This …

grafana 創建儀表盤_創建儀表盤前要問的三個問題

grafana 創建儀表盤可視化 (VISUALIZATIONS) It’s easier than ever to dive into dashboarding, but are you doing it right?深入儀表板比以往任何時候都容易,但是您這樣做正確嗎? Tableau, Power BI, and many other business intelligence tools …

qq群 voiceover_如何在iOS上使用VoiceOver為所有人構建應用程序

qq群 voiceoverby Jayven N由Jayven N 如何在iOS上使用VoiceOver為所有人構建應用程序 (How to build apps for everyone using VoiceOver on iOS) 輔助功能入門 (Getting started with accessibility) There’s always those topics that people don’t talk about enough. S…

IntelliJ IDEA代碼常用的快捷鍵(自查)

IntelliJ IDEA代碼常用的快捷鍵有: Alt回車 導入包,自動修正 CtrlN 查找類 CtrlShiftN 查找文件 CtrlAltL 格式化代碼 CtrlAltO 優化導入的類和包 AltInsert 生成代碼(如get,set方法,構造函數等) CtrlE或者AltShiftC 最近更改的代碼 CtrlR…

leetcode 1489. 找到最小生成樹里的關鍵邊和偽關鍵邊(并查集)

給你一個 n 個點的帶權無向連通圖,節點編號為 0 到 n-1 ,同時還有一個數組 edges ,其中 edges[i] [fromi, toi, weighti] 表示在 fromi 和 toi 節點之間有一條帶權無向邊。最小生成樹 (MST) 是給定圖中邊的一個子集,它連接了所有…

帶彩色字體的man pages(debian centos)

1234567891011121314151617181920212223242526272829303132333435363738我的博客已遷移到xdoujiang.com請去那邊和我交流簡介most is a paging program that displays,one windowful at a time,the contents of a file on a terminal. It pauses after each windowful and prin…

提取json對象中的數據,轉化為數組

var xx1 ["樂譜中的調號為( )調", "寫出a自然小調音階。", "以G為冠音,構寫增四、減五音程。", "調式分析。", "將下列樂譜移為C大調。", "正確組合以下樂譜。", "以下…

java 同步塊的鎖是什么,java – 同步塊 – 鎖定多個對象

我添加了另一個答案,因為我還沒有添加評論給其他人的帖子。>事實上,同步是用于代碼,而不是對象或數據。在同步塊中用作參數的對象引用表示鎖定。所以如果你有如下代碼:class Player {// Same instance shared for all players.…

大數據對社交媒體的影響_數據如何影響媒體,廣告和娛樂職業

大數據對社交媒體的影響In advance of our upcoming event — Data Science Salon: Applying AI and ML to Media, Advertising, and Entertainment, we asked our speakers, who are some of nation’s leading data scientists in the media, advertising, and entertainment…

Go-項目結構和代碼組織

簡介 做大量的輸入,通過對比、借鑒,加上自己的經驗,產出一個盡可能優的方案。 開源界優秀項目的結構示例 因為最新的 Go 版本已經使用 module 作為版本依賴,所以,所有項目的 vendor 我都忽略,建議直接使用 …

iref streams_如何利用Neo4j Streams并建立即時數據倉庫

iref streamsby Andrea Santurbano通過安德里亞桑圖爾巴諾(Andrea Santurbano) 如何利用Neo4j Streams并建立即時數據倉庫 (How to leverage Neo4j Streams and build a just-in-time data warehouse) In this article, we’ll show how to create a Just-In-Time Data Wareho…

Nodejs正則表達式函數之match、test、exec、search、split、replace使用詳解

1. Match函數使用指定的正則表達式函數對字符串驚醒查找,并以數組形式返回符合要求的字符串原型:stringObj.match(regExp)參數:stringObj 必選項,需要去進行匹配的字符串RegExp 必選項,指定的正則表達式返回值&#xf…

Zabbix 3.0 從入門到精通(zabbix使用詳解)

第1章 zabbix監控 1.1 為什么要監控 在需要的時刻,提前提醒我們服務器出問題了 當出問題之后,可以找到問題的根源 網站/服務器 的可用性 1.1.1 網站可用性 在軟件系統的高可靠性(也稱為可用性,英文描述為HA,High Avail…

python 裝飾器裝飾類_5分鐘的Python裝飾器指南

python 裝飾器裝飾類重點 (Top highlight)There’s no doubt that Python decorators are one of the more advanced and tougher-to-understand programming concepts. This doesn’t mean you should avoid learning them — as you encounter them in production code soone…

php中顏色的索引值,計算PHP中兩種顏色之間的平均顏色,使用索引號作為參考值...

我們假設為了討論的目的,每個顏色都有一個“值”.那么,你想要的就足夠簡單:$index 0.2;$val1 get_value_of_color($color1);$val2 get_value_of_color($color2);$newval $val1 * $index $val2 * (1 - $index);$newcolor get_color_from_value($newval);所以,很…

leetcode 989. 數組形式的整數加法

對于非負整數 X 而言,X 的數組形式是每位數字按從左到右的順序形成的數組。例如,如果 X 1231,那么其數組形式為 [1,2,3,1]。 給定非負整數 X 的數組形式 A,返回整數 XK 的數組形式。 示例 1: 輸入:A […