JavaScript?賦值,淺復(fù)制和深復(fù)制的區(qū)別
前言:
淺復(fù)制和深復(fù)制可以說是面試中很常見的一道題了,本文就來聊一聊JavaScript中的淺復(fù)制和深復(fù)制。
一、變量賦值
不知道會(huì)不會(huì)有人會(huì)和我一樣,會(huì)覺得淺復(fù)制就是通過=
操作符將一個(gè)變量賦值給另外一個(gè)變量。但實(shí)際上,淺復(fù)制和變量賦值之間是存在區(qū)別的。所以,我們先來了解一下變量賦值。
1.1 原始值和引用值
原始值(primitive value):最簡單的數(shù)據(jù),即:Undefined、Null、Boolean、Number、String、BigInt、Symbol這其中類型的值。保存原始值的變量是按值訪問的,我們操作的就是存儲(chǔ)在變量中的實(shí)際值。
引用值(reference value):有多個(gè)值構(gòu)成的對(duì)象,即 Object 類型。引用值是保存在內(nèi)存中的對(duì)象,但是 JavaScript 不允許直接訪問內(nèi)存位置,所以不能直接操作對(duì)象所在的內(nèi)存空間。在操作對(duì)象時(shí),實(shí)際操作的是該對(duì)象的引用(reference) 而非實(shí)際的對(duì)象本身。保存引用值得變量實(shí)際上存儲(chǔ)得是對(duì)象得引用,是按引用訪問得。
1.2 賦值
首先說明這里說的賦值,不是直接把引用值(例如:{})或者原始值(例如:false、1、"str"等)直接賦值給一個(gè)變量。而是通過變量把一個(gè)值賦值給另一個(gè)變量。
原始值賦值:保存原始值的變量是按值訪問的,所以通過變量把一個(gè)原始值賦值給另一個(gè)變量時(shí),原始值會(huì)被復(fù)制到新變量的位置。
let num1 = 5; let num2 = num1; console.log(num1, num2); // 5 5 num2 = 4; console.log(num1, num2); // 5 4
可以看出 num2
通過 num1
被賦值為5,保存的是同一個(gè)原始值。而且兩個(gè)變量相互獨(dú)立,互不干擾。
具體賦值過程如下:
引用值賦值:保存引用值的變量是按引用訪問的,通過變量把一個(gè)引用賦值給另一個(gè)變量時(shí),存儲(chǔ)在變量中的值也會(huì)被復(fù)制到新變量的位置。但是,這里復(fù)制的實(shí)際上是一個(gè)指向存儲(chǔ)在堆內(nèi)存中對(duì)象的指針。賦值后,兩個(gè)變量實(shí)際上指向同一個(gè)對(duì)象。所以兩個(gè)變量通過引用對(duì)對(duì)象的操作會(huì)互相影響。
let obj1 = {}; let obj2 = obj1; console.log(obj1); // {} console.log(obj2); // {} obj1.name = 'haha'; console.log(obj1); // { name: 'haha' } console.log(obj2); // { name: 'haha' } obj1.age = 24; console.log(obj1); // { name: 'haha', age: 24 } console.log(obj2); // { name: 'haha', age: 24 }
如上代碼,通過 obj1
將指向?qū)ο蟮囊觅x值給 obj2
后, obj1
和 obj2
保存了指向同一對(duì)象的引用,所以操作的是同一對(duì)象。
具體可見下圖:
注:如上兩圖來自《JavaScript 高級(jí)程序設(shè)計(jì)(第四版)》
接下來要說的淺復(fù)制和深復(fù)制就是針對(duì)引用值而言的。
二、淺復(fù)制(Shallow Copy)
我們先來看一篇博客中對(duì)于淺復(fù)制的定義:
An object is said to be shallow copied when the source top-level properties are copied without any reference and there exist a source property whose value is an object and is copied as a reference. If the source value is a reference to an object, it only copies that reference value to the target object.
對(duì)此,個(gè)人的理解淺復(fù)制就是復(fù)制該對(duì)象的的每個(gè)屬性,如果該屬性值是原始值,則復(fù)制該原始值,如果屬性值是一個(gè)對(duì)象,那么就復(fù)制該對(duì)象的引用。
即:淺復(fù)制將復(fù)制頂層屬性,但嵌套對(duì)象在原始(源)和拷貝(目標(biāo))之間共享
2.1 原生 JavaScript 中的淺復(fù)制
Object.assign()
Object.assign()
方法將所有可枚舉(Object.propertyIsEnumerable()
返回 true)和自有(Object.hasOwnProperty()
返回 true)屬性從一個(gè)或多個(gè)源對(duì)象復(fù)制(淺復(fù)制) 到目標(biāo)對(duì)象,返回修改后的對(duì)象。
如下代碼可以看出,淺復(fù)制和變量賦值不同,修改對(duì)象的屬性值互不影響。
const source = { a: 1, b: 2 }; const objCopied = Object.assign({}, source); console.log(objCopied); // { a: 1, b: 2 } source.a = 3; console.log(source); // { a: 3, b: 2 } console.log(objCopied); // { a: 1, b: 2 } objCopied.a = 4; console.log(source); // { a: 3, b: 2 } console.log(objCopied); // { a: 4, b: 2 }
對(duì)象內(nèi)的嵌套對(duì)象在源對(duì)象和拷貝對(duì)象之間還是共享的,如上代碼,修改對(duì)象內(nèi)對(duì)象的屬性時(shí)會(huì)相互影響。
const source = { a : {b : 1} }; const objCopied = Object.assign({}, source); console.log(objCopied); // { a: { b: 1 } } source.a.b = 3; console.log(source); // { a: { b: 3 } } console.log(objCopied); // { a: { b: 3 } }
但是注意如下代碼中,source.a = {};
修改的是源對(duì)象中屬性的值,這個(gè)并不共享。
const source = { a : {b : 1} }; const objCopied = Object.assign({}, source); console.log(objCopied); // { a: { b: 1 } } source.a = {}; console.log(source); // { a: {} } console.log(objCopied); // { a: { b: 1 } }
展開運(yùn)算符(...):展開語法(Spread syntax), 可以在函數(shù)調(diào)用/數(shù)組構(gòu)造時(shí), 將數(shù)組表達(dá)式或者string在語法層面展開;還可以在構(gòu)造字面量對(duì)象時(shí), 將對(duì)象表達(dá)式按key-value的方式展開。
const source = { a : {b : 1}, c: 2 }; const objCopied = {...source} console.log(objCopied); // { a: { b: 1 }, c: 2 } source.c = 3; console.log(source); // { a: { b: 1 }, c: 3 } console.log(objCopied); // { a: { b: 1 }, c: 2 } source.a.b = 3; console.log(source); // { a: { b: 3 }, c: 3 } console.log(objCopied); // { a: { b: 3 }, c: 2 }
2.2 淺復(fù)制的手動(dòng)實(shí)現(xiàn)
function shallowClone(source) { // 如果是原始值,直接返回 if (typeof source !== 'object') { return source; } // 拷貝后的對(duì)象 const copied = Array.isArray(source) ? [] : {}; // 遍歷對(duì)象的key for(let key in source) { // 如果key是對(duì)象的自有屬性 if(source.hasOwnProperty(key)) { // 復(fù)制屬性 copied[key] = source[key] } } // 返回拷貝后的對(duì)象 return copied; }
三、深復(fù)制(Deep Copy)
首先來看深復(fù)制的定義:
A deep copy will duplicate every object it encounters. The copy and the original object will not share anything, so it will be a copy of the original.
與淺復(fù)制不同時(shí),當(dāng)源對(duì)象屬性的值為對(duì)象時(shí),賦值的是該對(duì)象,而不是對(duì)象的引用。所以深復(fù)制中,源對(duì)象和拷貝對(duì)象之間不存在任何共享的內(nèi)容。
2.1 原生 JavaScript 中的深復(fù)制
JSON.parse(JSON.stringify(object))
JavaScript 中最常見的深復(fù)制的方法就是JSON.parse(JSON.stringify(object))
如下代碼所示,深復(fù)制中源對(duì)象和拷貝對(duì)象不共享任何內(nèi)容,即使是嵌套對(duì)象。
let obj = { a: 1, b: { c: 2, }, } let newObj = JSON.parse(JSON.stringify(obj)); obj.b.c = 20; console.log(obj); // { a: 1, b: { c: 20 } } console.log(newObj); // { a: 1, b: { c: 2 } }
2.2 深復(fù)制的手動(dòng)實(shí)現(xiàn)
function deepClone(source) { // 如果是原始值,直接返回 if (typeof source !== 'object') { return source; } // 拷貝后的對(duì)象 const copied = Array.isArray(source) ? [] : {}; // 遍歷對(duì)象的key for(let key in source) { // 如果key是對(duì)象的自有屬性 if(source.hasOwnProperty(key)) { // 深復(fù)制 copied[key] = deepClone(source[key]); } } return copied; }
有關(guān)淺復(fù)制和深復(fù)制的手動(dòng)實(shí)現(xiàn),這里只是簡單實(shí)現(xiàn)了一下。其中還有很多細(xì)節(jié)未實(shí)現(xiàn),具體的實(shí)現(xiàn)大家可以參見 lodash 中的實(shí)現(xiàn)。
小結(jié)
- 賦值操作符是把一個(gè)對(duì)象的引用賦值給一個(gè)變量,所以變量中存儲(chǔ)的是對(duì)象的引用
- 淺復(fù)制是復(fù)制源對(duì)象的每個(gè)屬性,但如果屬性值是對(duì)象,那么復(fù)制的是這個(gè)對(duì)象的引用。所以源對(duì)象和拷貝對(duì)象之間共享嵌套對(duì)象。
- 深復(fù)制與淺復(fù)制不同的地方在于,如果屬性值為對(duì)象,那么會(huì)復(fù)制該對(duì)象。源對(duì)象和拷貝對(duì)象之間不存在共享的內(nèi)容。
到此這篇關(guān)于JavaScript 賦值,淺復(fù)制和深復(fù)制的區(qū)別的文章就介紹到這了,更多相關(guān)JS 賦值內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript forEach 方法跳出循環(huán)的操作方法
這篇文章主要介紹了JavaScript forEach 方法跳出循環(huán)的操作方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01php 解壓zip壓縮包內(nèi)容到指定目錄的實(shí)例
下面小編就為大家分享一篇php 解壓zip壓縮包內(nèi)容到指定目錄的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01單行 JS 實(shí)現(xiàn)移動(dòng)端金錢格式的輸入規(guī)則
這篇文章主要介紹了單行 JS 實(shí)現(xiàn)移動(dòng)端金錢格式的輸入規(guī)則,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-05-05JS函數(shù)arguments數(shù)組獲得實(shí)際傳參數(shù)個(gè)數(shù)的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄狫S函數(shù)arguments數(shù)組獲得實(shí)際傳參數(shù)個(gè)數(shù)的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-05-05javascript實(shí)現(xiàn)貪吃蛇經(jīng)典游戲
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)貪吃蛇經(jīng)典游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04JavaScript canvas實(shí)現(xiàn)跟隨鼠標(biāo)事件
這篇文章主要為大家詳細(xì)介紹了JavaScript canvas實(shí)現(xiàn)跟隨鼠標(biāo)事件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02基于javascript實(shí)現(xiàn)tab切換特效
這篇文章主要介紹了基于javascript實(shí)現(xiàn)tab切換特效的相關(guān)資料,具有一定的參考價(jià)值,需要的朋友可以參考下2016-03-03