javascript 物件的深拷貝與淺拷貝
阿新 • • 發佈:2021-01-14
今天我們來談一談物件的深拷貝和淺拷貝吧
我們都知道,js資料型別主要分為兩大類 基礎資料型別和引用(複雜)資料型別。
- 基礎資料型別存在於棧記憶體中,當被拷貝時,會建立一個完全相等的變數
- 而引用資料型別存在於堆中,儲存的是一個記憶體空間,而賦值給變數的,僅僅是這個記憶體空間的一個引用而已。而就會出現一個問題,當我們將一個物件賦值給另一個變數時,賦值的是物件的引用,必然導致兩個變數都指向同一個記憶體空間,其中一個改變時,必然會影響到另一個。
淺拷貝和深拷貝的理解
什麼是淺拷貝
簡單理解就是
自己建立一個新的物件,來接受你要重新複製或引用的物件值。如果物件屬性是基本的資料型別,複製的就是基本型別的值給新物件;但如果屬性是引用資料型別,複製的就是記憶體中的地址,如果其中一個物件改變了這個記憶體中的地址,肯定會影響到另一個物件。
可以實現淺拷貝的方法
1、Object.assign()
- object.assign 是es6新增的一個方法
- 用於合併多個物件到目標物件中
- 第一個引數是目標物件,後面的引數是源物件
var obj = {}
var source1 = {
name: '張三',
age: 18,
like: {
music: '老鼠愛大米'
}
}
Object.assign(obj, source1)
console.log(obj)
source1.like.music= "沙漠駱駝"
console.log(obj)
// 輸出
{ name: '張三', age: 18, like: { music: '老鼠愛大米' } }
{ name: '張三', age: 18, like: { music: '沙漠駱駝' } }
從上面可以看出,修改source1的like屬性下的music屬性值時,會對obj產生影響
2、擴充套件運算子
- 擴充套件運算子可以展開物件或者陣列中的選項 obj = { …obj2 }
var obj = {}
var source1 = {
name: '張三',
age: 18,
like: {
music: '老鼠愛大米'
}
}
obj = { ...source1 }
console.log(obj)
source1.like. music= "沙漠駱駝"
console.log(obj)
// 輸出
{ name: '張三', age: 18, like: { music: '老鼠愛大米' } }
{ name: '張三', age: 18, like: { music: '沙漠駱駝' } }
可以發現,和Object.assign() 一樣的結果
同時,使用這兩種方式還需要注意
- 它不會拷貝物件的繼承屬性
- 它只能拷貝可列舉的屬性
- 它可以拷貝 Symbol 型別的屬性
var obj = {
name: '張三',
age: 18,
sym: Symbol(123)
}
Object.defineProperty(obj, 'aaa', {
value: '這是一個不可列舉的屬性',
enumerable: false // 設定為false代表此屬性不可列舉
})
var obj2 = { ...obj }
console.log(obj2)
// 輸出
{ name: '張三', age: 18, sym: Symbol(123) }
從上面可以看出,symbol屬性被拷貝了,但是屬性aaa並沒有被拷貝
3、陣列拷貝方法
concat ()
使用方法我就不多說了,不懂得看我上一篇文章吧
var arr = [{name: '張三'}, 2, {age: 18}]
var brr = arr.concat()
console.log(brr)
arr[0].name = '李四'
console.log(brr)
// 輸出
[ { name: '張三' }, 2, { age: 18 } ]
[ { name: '李四' }, 2, { age: 18 } ]
可以看出,concat()也是淺拷貝
slice()
使用方法見上一篇文章
var arr = [{name: '張三'}, 2, {age: 18}]
var brr = arr.slice()
console.log(brr)
arr[0].name = '李四'
console.log(brr)
// 輸出
[ { name: '張三' }, 2, { age: 18 } ]
[ { name: '李四' }, 2, { age: 18 } ]
和concat一樣,屬於淺拷貝
擴充套件運算子
陣列擴充套件運算子和物件得一模一樣,我就不另外說了
深拷貝
我們可能大多時候最常用到的深拷貝就是JSON.stringify() 了。相信大家都用過
原理就是先將物件轉為json字串,再轉換為物件。
var obj = {
name: '張三',
like: {
music: '老鼠愛大米',
color: 'red',
animate: {
name: '狗'
}
}
}
var obj2 = JSON.parse(JSON.stringify(obj))
obj.like.music = '沙漠駱駝'
obj.like.animate.name = '熊貓'
console.log(obj)
console.log(obj2)
// 輸出
{
name: '張三',
like: { music: '沙漠駱駝', color: 'red', animate: { name: '熊貓' } }
}
{
name: '張三',
like: { music: '老鼠愛大米', color: 'red', animate: { name: '狗' } }
}
可以看出,更改obj 不會影響到拷貝出來的obj2
但是這種方法也存在很多問題
- 拷貝的物件的值中如果有函式、undefined、symbol 這幾種型別,經過 JSON.stringify 序列化之後的字串中這個鍵值對會消失,導致無法拷貝到
- 拷貝 Date 引用型別會變成字串
- 無法拷貝不可列舉的屬性
- 拷貝 RegExp 引用型別會變成空物件
- 物件中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的結果會變成 null
var obj = {
base: {
name: '張三'
}
}
obj.date = new Date(0)
obj.un = undefined
obj.nu = null
obj.infinity = Infinity
obj.nan = NaN
obj.sym = Symbol(123)
obj.reg = /^123/
Object.defineProperty(obj, 'aaa', {
value: '這是一個不可列舉的屬性',
enumerable: false // 設為false代表不可列舉
})
var obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj)
console.log(obj2)
// 輸出
{
base: { name: '張三' },
date: 1970-01-01T00:00:00.000Z,
un: undefined,
nu: null,
infinity: Infinity,
nan: NaN,
sym: Symbol(123),
reg: /^123/
}
{
base: { name: '張三' },
date: '1970-01-01T00:00:00.000Z',
nu: null,
infinity: null,
nan: null,
reg: {}
}
從上面可以看出
- Date物件變成字串了
- infinity 和 NaN 變成null了
- un屬性的值是undefined,並沒有被拷貝到,因為這個屬性在序列號時消失了,sym屬性也是一樣消失了
- RegExp引用型別變成空物件了
- aaa這個不可列舉的屬性也沒有被拷貝到
那麼,如何實現一個完整的深拷貝方法呢,那就要看我們該如何一一來解決以上問題了
- 拷貝引用型別屬性時,我們可以通過遞迴來迴圈拷貝
- 如果屬性是簡單資料型別,包括undefined,infinity 和 NaN, 則直接遞迴賦值
- 拷貝Date物件時,我們可以直接重新生成一個新的Date例項返回
- 拷貝RegExp型別時,也是一樣,直接生成一個新的RegExp例項返回
- 拷貝Symbol屬性和不可列舉的屬性時,使用Reflect.ownKeys (什麼是Reflect.ownKeys)方法
function isObject (obj) {
if ((typeof obj === 'object' || typeof obj === 'function') && (obj !== null)) {
return true
}
}
function deepClone (obj) {
if (obj.constructor === Date) {
return new Date(obj) // 返回一個新例項
}
if (obj.constructor === RegExp) {
return new RegExp(obj) // 返回一個新例項
}
let allDesc = Object.getOwnPropertyDescriptors(obj)
// 遍歷傳入引數所有鍵的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isObject(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key]) : obj[key]
}
return cloneObj
}
var obj = {
base: {
name: '張三'
}
}
obj.date = new Date(0)
obj.un = undefined
obj.nu = null
obj.infinity = Infinity
obj.nan = NaN
obj.sym = Symbol(123)
obj.reg = /^123/
Object.defineProperty(obj, 'aaa', {
value: '這是一個不可列舉的屬性',
enumerable: false // 設為false代表不可列舉
})
var obj2 = deepClone(obj)
console.log(obj)
console.log(obj2)
// 輸出
{
base: { name: '張三' },
date: 1970-01-01T00:00:00.000Z,
un: undefined,
nu: null,
infinity: Infinity,
nan: NaN,
sym: Symbol(123),
reg: /^123/
}
{
base: { name: '張三' },
date: 1970-01-01T00:00:00.000Z,
un: undefined,
nu: null,
infinity: Infinity,
nan: NaN,
sym: Symbol(123),
reg: /^123/
}
從結果可以看出,我們實現的深拷貝函式確實實現了物件的一個深拷貝功能。
深拷貝與淺拷貝就寫到這了。有問題歡迎指出