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 = obj
obj2.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 = 2
console.log(obj)
console.log(obj2);
obj.b.c = 2
console.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ù)組的淺拷貝,使用場景比較局限。
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ù)庫 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并不是那么完美的,它也有局限性。文章來源:http://www.zghlxwxcb.cn/news/detail-474252.html
- 拷貝的對(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 (中國標(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 {
//如果不是,簡單復(fù)制
objClone[key] = obj[key];
}
}
}
return objClone;
}
3.2.2 函數(shù)庫lodash
lodash是一個(gè)著名的javascript原生庫,不需要引入其他第三方依賴。是一個(gè)意在提高開發(fā)者效率,提高JS原生方法性能的JS庫。簡單的說就是,很多方法lodash已經(jīng)幫你寫好了,直接調(diào)用就行,不用自己費(fèi)盡心思去寫了,而且可以統(tǒng)一方法的一致性。Lodash使用了一個(gè)簡單的_ 符號(hào),就像Jquery的 $ 一樣,十分簡潔。lodash函數(shù)庫提供 _.cloneDeep用來做深拷貝。文章來源地址http://www.zghlxwxcb.cn/news/detail-474252.html
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)用場景
- 淺拷貝主要用于你需要拷貝的對(duì)象的數(shù)據(jù)結(jié)構(gòu)只有基礎(chǔ)數(shù)據(jù)類型,并且你不想改變?cè)瓟?shù)據(jù)類型或者需要對(duì)比操作前后的數(shù)據(jù)。
- 深拷貝主要用于你想操作該數(shù)據(jù),但是又不想影響到原數(shù)據(jù)的時(shí)候,就可以進(jìn)行深拷貝。
到了這里,關(guān)于詳解js中的淺拷貝與深拷貝的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!