遵義疫情最新數(shù)據(jù)消息百度小程序優(yōu)化排名
詳解js中的淺拷貝與深拷貝
- 1、前言
- 1.1 棧(stack)和堆(heap)
- 1.2 基本數(shù)據(jù)類型和引用數(shù)據(jù)類型
- 1.2.1 概念
- 1.2.2 區(qū)別
- 1.2.3 基本類型賦值方式
- 1.2.4 引用類型賦值方式
- 2、淺拷貝
- 2.1 概念
- 2.2 常見的淺拷貝方法
- 2.2.1 Object.assign()
- 2.2.2 擴(kuò)展運(yùn)算符(...)
- 2.2.3 Array.concat()
- 2.2.4 Array.slice()
- 3、深拷貝
- 3.1 概念
- 3.2 常見的深拷貝方法
- 3.2.1 JSON.parse(JSON.stringify(obj))
- 3.2.2 遞歸
- 3.2.2 函數(shù)庫(kù)lodash
- 4、總結(jié)
- 5、應(yīng)用場(chǎng)景
1、前言
1.1 棧(stack)和堆(heap)
- 棧(stack):由操作系統(tǒng)自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧;
- 堆(heap):一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收,分配方式倒是類似于鏈表
1.2 基本數(shù)據(jù)類型和引用數(shù)據(jù)類型
1.2.1 概念
- 基本數(shù)據(jù)類型:Number、String、Boolean、Null、 Undefined、Symbol(ES6);
- 引用數(shù)據(jù)類型:Object、Array、Function、Date、RegExp、Map、Set等。
1.2.2 區(qū)別
兩者區(qū)別:
- 內(nèi)存地址分配:
1)基本數(shù)據(jù)類型:將值存儲(chǔ)在棧中 ,棧中存放的是對(duì)應(yīng)的值
2)引用數(shù)據(jù)類型:將對(duì)應(yīng)的值存儲(chǔ)在堆中,棧中存放的是指向堆內(nèi)存的地址- 賦值變量:
1)基本數(shù)據(jù)類型:是生成相同的值,兩個(gè)對(duì)象對(duì)應(yīng)不同的地址
2)引用數(shù)據(jù)類型:是將保存對(duì)象的內(nèi)存地址賦值給另一個(gè)變量。也就是兩個(gè)變量指向堆內(nèi)存中同一個(gè)對(duì)象
let a = 10;let b = a; // 賦值操作b = 100;console.log(a); // 10
總結(jié):
a是基本類型,存儲(chǔ)在棧中;把a(bǔ)賦值給b,雖然兩個(gè)變量的值相等,但是兩個(gè)變量保存了兩個(gè)不同的內(nèi)存地址。
1.2.3 基本類型賦值方式
1.2.4 引用類型賦值方式
let obj1 = {}let obj2 = objobj2.name = '李四'console.log(obj.name) // 李四
理解:obj1是引用類型,將數(shù)據(jù)存放在堆內(nèi)存中,而棧中存放的是內(nèi)存地址.在obj1賦值給obj2,實(shí)際是將obj1的引用地址復(fù)制了一份給了obj2,實(shí)際上他們共同指向了同一個(gè)堆內(nèi)存對(duì)象,所以更改obj2會(huì)對(duì)obj1產(chǎn)生影響
2、淺拷貝
2.1 概念
會(huì)在棧中開辟另一塊空間,并將被拷貝對(duì)象的棧內(nèi)存數(shù)據(jù)完全拷貝到該塊空間中,即基本數(shù)據(jù)類型的值會(huì)被完全拷貝,而引用類型的值則是拷貝了“指向堆內(nèi)存的地址”。
2.2 常見的淺拷貝方法
- Object.assign()
- 擴(kuò)展運(yùn)算符(…)
- Array.concat()
- Array.slice()
2.2.1 Object.assign()
object.assign 是 ES6 中 object 的一個(gè)方法,該方法可以用于JS 對(duì)象的合并等多個(gè)用途,其中一個(gè)用途就是可以進(jìn)行淺拷貝。
object.assign 的語法為:Object.assign(target, …sources)
var obj = { x: 1, y: 2,z: { num: 10 }
}
var newObj = {}
Object.assign(newObj, obj)
newObj.y = 3
console.log(obj)
console.log(newObj)
運(yùn)行結(jié)果如下:
注意:
- Object.assign()不會(huì)拷貝對(duì)象的繼承屬性;
- Object.assign()不會(huì)拷貝對(duì)象的不可枚舉的屬性;
- Object.assign()可以拷貝 Symbol 類型的屬性。
2.2.2 擴(kuò)展運(yùn)算符(…)
擴(kuò)展運(yùn)算符的語法為:let cloneObj = { …obj };
/* 對(duì)象的拷貝 */
const obj = {a: 1,b: {c: 1}
}
const obj2 = {...obj
}
obj.a = 2console.log(obj)
console.log(obj2); obj.b.c = 2console.log(obj)
console.log(obj2); /* 數(shù)組的拷貝 */
let arr = [1, 2, 3];
let newArr = [...arr]; // 跟arr.slice()是一樣的效果
運(yùn)行結(jié)果如下:
注意:擴(kuò)展運(yùn)算符 和 object.assign 有同樣的缺陷,也就是實(shí)現(xiàn)的淺拷貝的功能差不多,但是如果屬性都是基本類型的值,使用擴(kuò)展運(yùn)算符進(jìn)行淺拷貝會(huì)更加方便。
2.2.3 Array.concat()
var obj1 = ["Chinese", { "name": "zs" }, "French"]
var obj2 = obj1.concat()
obj2[1].name = "ls"
obj2[2] = "China"
console.log(obj1, obj2);
運(yùn)行結(jié)果如下:
注意:數(shù)組的 concat 方法其實(shí)也是淺拷貝,所以連接一個(gè)含有引用類型的數(shù)組時(shí),需要注意修改原數(shù)組中的元素的屬性,因?yàn)樗鼤?huì)影響拷貝之后連接的數(shù)組。不過 concat 只能用于數(shù)組的淺拷貝,使用場(chǎng)景比較局限。
2.2.4 Array.slice()
slice 的語法為:arr.slice(begin, end);
var arr = [2, 4, 6, { y: 10 }]
var newArr = arr.slice()
newArr[0] = 10
newArr[3].x = 20
newArr[3].y = 30
console.log(arr)
console.log(newArr)
運(yùn)行結(jié)果如下:
注意:slice 方法也比較有局限性,因?yàn)樗鼉H僅針對(duì)數(shù)組類型。slice 方法會(huì)返回一個(gè)新的數(shù)組對(duì)象,這一對(duì)象由該方法的前兩個(gè)參數(shù)來決定原數(shù)組截取的開始和結(jié)束位置,是不會(huì)影響和改變?cè)紨?shù)組的。但是,數(shù)組元素是引用類型的話,也會(huì)影響到原始數(shù)組。
3、深拷貝
3.1 概念
深拷貝是拷貝多層,每一級(jí)別的數(shù)據(jù)都會(huì)拷貝出來。
3.2 常見的深拷貝方法
- JSON.parse(JSON.stringify(obj))
- 遞歸方法
- 函數(shù)庫(kù) lodash
3.2.1 JSON.parse(JSON.stringify(obj))
原理:用 JSON.stringify 將對(duì)象轉(zhuǎn)成 JSON 字符串,再用 JSON.parse()
把字符串解析成對(duì)象。一去一來,新的對(duì)象就產(chǎn)生了,而且對(duì)象會(huì)開辟新的棧,實(shí)現(xiàn)深拷貝。
let a = {name: '張三', age: 19, like: ['打籃球', '唱歌', '跳舞']}
let b = JSON.parse(JSON.stringify(a))
a.name = '李四'
a.like[0] = '睡覺'
console.log(a)
console.log(b)
運(yùn)行結(jié)果如下:
但是,JSON.stringify并不是那么完美的,它也有局限性。
- 拷貝的對(duì)象的值中如果有函數(shù)、undefined、symbol 這幾種類型,經(jīng)過 JSON.stringify 序列化之后的字符串中這個(gè)鍵值對(duì)會(huì)消失;
- 拷貝 Date 引用類型會(huì)變成字符串;
- 無法拷貝不可枚舉的屬性;
- 無法拷貝對(duì)象的原型鏈;
- 拷貝 RegExp 引用類型會(huì)變成空對(duì)象;
- 對(duì)象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的結(jié)果會(huì)變成 null;
- 無法拷貝對(duì)象的循環(huán)應(yīng)用,即對(duì)象成環(huán) (obj[key] = obj)。
let obj = {func: function () { alert(1) },obj: { a: 1 },arr: [1, 2, 3],und: undefined,reg: /123/,date: new Date(0),NaN: NaN,infinity: Infinity,sym: Symbol('1')
}Object.defineProperty(obj, 'innumerable', {enumerable: false,value: 'innumerable'
});console.log('obj', obj); // { NaN: NaN , arr: (3) [1, 2, 3] ,date: Thu Jan 01 1970 08:00:00 GMT+0800 (中國(guó)標(biāo)準(zhǔn)時(shí)間) {}, func: ?(), infinity: Infinity , obj: { a: 1 } , reg: /123/, sym: Symbol(1), und: undefined, innumerable: "innumerable" }const str = JSON.stringify(obj);const obj1 = JSON.parse(str);console.log('obj1', obj1); // { NaN: null, arr: (3) [1, 2, 3], date: "1970-01-01T00:00:00.000Z", infinity: null, obj: {a: 1}, reg: {} }
3.2.2 遞歸
function deepCopyTwo(obj) {let objClone = Array.isArray(obj) ? [] : {};if (obj && typeof obj == 'object') {for (const key in obj) {//判斷obj子元素是否為對(duì)象,如果是,遞歸復(fù)制if (obj[key] && typeof obj[key] === "object") {objClone[key] = deepCopyTwo(obj[key]);} else {//如果不是,簡(jiǎn)單復(fù)制objClone[key] = obj[key];}}} return objClone;}
3.2.2 函數(shù)庫(kù)lodash
lodash是一個(gè)著名的javascript原生庫(kù),不需要引入其他第三方依賴。是一個(gè)意在提高開發(fā)者效率,提高JS原生方法性能的JS庫(kù)。簡(jiǎn)單的說就是,很多方法lodash已經(jīng)幫你寫好了,直接調(diào)用就行,不用自己費(fèi)盡心思去寫了,而且可以統(tǒng)一方法的一致性。Lodash使用了一個(gè)簡(jiǎn)單的_ 符號(hào),就像Jquery的 $ 一樣,十分簡(jiǎn)潔。lodash函數(shù)庫(kù)提供 _.cloneDeep用來做深拷貝。
let _ = require('lodash');let obj = {a:1,b:{f:{g:1}},c:[1,2,3]};let newObj = _cloneDeep(obj);console.log(obj.b.f === newObj.b.f); //true
4、總結(jié)
- 淺拷貝就是只拷貝基礎(chǔ)數(shù)據(jù)類型的數(shù)據(jù),并且數(shù)據(jù)只有單一的一層,而深拷貝用于拷貝具有復(fù)雜的數(shù)據(jù)類型的數(shù)據(jù)
5、應(yīng)用場(chǎng)景
- 淺拷貝主要用于你需要拷貝的對(duì)象的數(shù)據(jù)結(jié)構(gòu)只有基礎(chǔ)數(shù)據(jù)類型,并且你不想改變?cè)瓟?shù)據(jù)類型或者需要對(duì)比操作前后的數(shù)據(jù)。
- 深拷貝主要用于你想操作該數(shù)據(jù),但是又不想影響到原數(shù)據(jù)的時(shí)候,就可以進(jìn)行深拷貝。