※超省錢租車方案
商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!
背景
在日常開發中,偶爾會遇到需要複製對象的情況,需要進行對象的複製。
由於現在流行標題黨,所以,一文帶你了解js數據儲存及深複製(深拷貝)與淺複製(淺拷貝)
理解
首先就需要理解 js 中的數據類型了
js 數據類型包含
基礎類型
:String
、Number
、null
、undefined
、Boolean
以及ES6
引入的Symbol
、es10
中的BigInt
引用類型
:Object
由於 js 對變量的儲存是棧內存
、堆內存
完成的。
基礎類型
將數據保存在棧內存
中引用類型
將數據保存在堆內存
中
由於 js 在數據讀取和寫入的時候,對基礎類型
是直接讀寫棧內存
中的數據,引用類型
是將一個內存地址保存在棧內存中,讀寫都是修改棧內存中指向堆內存的地址
以如下代碼為例
let obj = {
a:1,
arr:[1,3,5,7,9],
b:2,
c:{
num:100
}
}
let num = 10
在內存中的表現為
我們聲明個obj1
let obj1 = obj;
console.log(obj1 == obj);//true
因為這個賦值,把內存變成了這樣
然後,內存中只是給js棧內存新增了一個指向堆內存
的地址而已,這種就叫做淺複製
。因為如圖可以看到,如果我們修改obj.a
的話,實際修改的是堆內存0x88888888
中的變量a
,由於obj1
也指向這個地址,所以obj1.a
也被修改了
深複製
是指,不單單複製引用地址,連堆內存都複製一遍,使obj
和obj1
不指向同一個地址。
代碼
分開來看深複製
與淺複製
淺複製
由上述圖可知,淺複製只是複製了堆內存的引用地址,通常在業務需求中出現的淺複製
是指複製引用對象的第一層,也就是,基本類型
複製新值,引用類型
複製引用地址
淺複製
可以使用的方案有循環賦值
、擴展運算符
、object.assign()
,
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。
let obj = {
a:1,
arr:[1,3,5,7,9],
b:2,
c:{
num:100
}
}
function clone1(obj){ // 使用循環賦值
let b = {};
for(let key in obj){
b[key] = obj[key]
}
return b
}
function clone2(obj){ // 使用擴展運算符
let b = {
...obj
};
return b
}
function clone3(obj){ // 使用object.assign()
let b = {};
Object.assign(b,obj)
return b
}
let obj1 = clone1(obj);
let obj2 = clone2(obj);
let obj3 = clone3(obj);
console.log(obj1 === obj); //false 代表複製成功了
console.log(obj2 === obj); //false 代表複製成功了
console.log(obj3 === obj); //false 代表複製成功了
console.log('obj0.c.num修改前',obj.c.num); //100
console.log('obj1.c.num修改前',obj1.c.num); //100
console.log('obj2.c.num修改前',obj2.c.num); //100
console.log('obj3.c.num修改前',obj3.c.num); //100
obj0.c.num = 555;
console.log('obj0.c.num修改后',obj.c.num); //555
console.log('obj1.c.num修改后',obj1.c.num); //555
console.log('obj2.c.num修改后',obj2.c.num); //555
console.log('obj3.c.num修改后',obj3.c.num); //555
由於是淺複製,所以引用類型只是複製了內存地址,修改其中一個對象的子屬性后,引用這個地址的值都會被修改。
淺克隆圖解如下
深複製
由於淺複製只是複製第一層,為了解決引用類型的複製,需要使用深複製來完成對象的複製,基本類型
複製新值,引用類型
開闢新的堆內存
。
深複製
可以使用的方案有JSON.parse(JSON.stringify(obj))
、循環賦值
。
JSON.parse(JSON.stringify(obj))
let obj = {
a:1,
arr:[1,3,5,7,9],
c:{
num:100
},
fn:function(){
console.log(1)
},
date:new Date(),
reg:/\.*/g
}
function clone1(obj){ // 使用JSON.parse(JSON.stringify(obj))
return JSON.parse(JSON.stringify(obj))
}
let obj1 = clone1(obj);
console.log(obj === obj1); //false 代表複製成功了
obj.c.num = 555;
console.log(obj.c.num,obj1.c.num) // 555,100
看起來是複製成功了!!~地址也變了,修改obj
,obj1
的引用地址不會跟着變化。
但是我們來console
一下obj
以及obj1
console.log(obj)
console.log(obj1)
似乎發現了離奇的事情,只有obj.a
以及obj.c
正確的複製了,日期類型
、方法
、正則表達式
均沒有複製成功,發生了一些奇怪的事情
循環賦值 deepClone
那麼為了解決這種事情,就需要寫一個deepClone
方法來完成深複製了,參考了許多開源庫的寫法,將所有的複製項單獨拆出,方便未來對特殊類型進行擴展,也防止不同功能間的變量互相干擾
//既然是深複製,一定要傳入一個object,再return 一個新的 Object
function deepClone(obj){
let newObj;
if(obj instanceof Array){ // 數組的話,要new一個數組
newObj = []
}else if(obj instanceof Object){ // 對象的話,要new一個對象
newObj = {}
}
if(obj === null) {
return cloneNull(obj)
}
if(typeof obj=='function'){
return cloneFunction(obj)
}
if(typeof obj!='object') {
return cloneOther(obj)
}
if(obj instanceof RegExp) {
return cloneRegExp(obj)
}
if(obj instanceof Date){
return cloneDate(obj)
}
if(obj instanceof Array){
for(let index in obj){
newObj[index] = deepClone(obj[index]); // 對數組子項進行複製
}
}
if(obj instanceof Object){
for(let key in obj){
newObj[key] = deepClone(obj[key]); // 對對象子項進行複製
}
}
return newObj;
}
function cloneNull(obj){ // 複製NULL
return obj
}
function cloneFunction(obj){ // 複製方法,
// 複製一個新方法,將原方法轉成字符串,並new一個新的function
return new Function('return '+obj.toString())()
}
function cloneOther(obj){ // 複製非對象的數據
return obj
}
function cloneRegExp(obj){ // 複製正則對象
return new RegExp(obj)
}
function cloneDate(obj){ // 複製日期對象
return new Date(obj)
}
這樣一個基本上滿足功能的深複製就完成了。先測試一下
let obj = {
a:1,
arr:[1,3,5,7,9],
c:{
num:100
},
fn:function(){
console.log(1)
},
date:new Date(),
reg:/\.*/g
}
let obj1 = deepClone(obj);
console.log(obj.c === obj1.c); // false 代表複製成功
console.log(obj.fn === obj1.fn);// false 代表複製成功
console.log(obj.date === obj1.date);// false 代表複製成功
console.log(obj.reg === obj1.reg);// false 代表複製成功
再console
一下
console.log(obj)
console.log(obj1)
這樣,就完成了deepClone
深複製方法
經過深複製后,圖解如下
優化 deepClone
上述代碼還有優化空間,參考了lodash
庫,在進行 new 對象時,可以使用 constructor
構造函數 來進行創建新的實例,這樣
- 可以不用判斷遞歸中,是數組還是對象
- 如果深複製的某一項是某個原型的實例,深複製完成后,依然是該原型的實例
function deepClone(obj){
let newObj = new obj.constructor;
if(obj === null) {
return cloneNull(obj)
}
if(typeof obj=='function'){
return cloneFunction(obj)
}
if(typeof obj!='object') {
return cloneOther(obj)
}
if(obj instanceof RegExp) {
return cloneRegExp(obj)
}
if(obj instanceof Date){
return cloneDate(obj)
}
if(obj instanceof Array){
for(let index in obj){
newObj[index] = deepClone(obj[index]); // 對數組子項進行複製
}
}
if(obj instanceof Object){
for(let key in obj){
newObj[key] = deepClone(obj[key]); // 對對象子項進行複製
}
}
return newObj;
}
function cloneNull(obj){ // 複製NULL
return obj
}
function cloneFunction(obj){ // 複製方法,
// 複製一個新方法,將原方法轉成字符串,並new一個新的function
return new Function('return '+obj.toString())()
}
function cloneOther(obj){ // 複製非對象的數據
return obj
}
function cloneRegExp(obj){ // 複製正則對象
return new RegExp(obj)
}
function cloneDate(obj){ // 複製日期對象
return new Date(obj)
}
最終版本 deepClone
然後可以有一個合併版本的,比較節省代碼,將下方區分開的複製方法,合併到deepClone
中,可以極大地減少代碼體積
function deepClone(obj){ //
let newObj = new obj.constructor;
if(obj === null) return obj
if(typeof obj=='function') return new Function('return '+obj.toString())()
if(typeof obj!='object') return obj
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Date) return new Date(obj)
// 運行到這裏,基本上只存在數組和對象兩種類型了
for(let index in obj){
newObj[index] = deepClone(obj[index]); // 對子項進行遞歸複製
}
return newObj;
}
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污