在JavaScript中存在兩巨量資料型別:基本型別、參照型別。
基本資料型別存放在棧中,是一段簡單的資料段,資料大小確定,記憶體空間大小可以分配,是直接按值存放的,可以按值存取。
參照資料型別存放在堆中,變數在棧中儲存的是指向堆記憶體的地址值,這個地址值指向對應的物件型別,存取堆記憶體中的物件是通過地址值存取的。
淺拷貝,指的是建立新的資料,這個資料有著原始資料屬性值的一份精確拷貝。
如果屬性是基本型別,拷貝的就是基本型別的值。如果屬性是參照型別,拷貝的就是記憶體地址。
即淺拷貝是拷貝一層。
下面簡單實現一個淺拷貝:
function shallowClone (obj) {
const newObj = {}
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
newObj[prop] = obj[prop]
}
}
return newObj
}
在JavaScript中,存在淺拷貝的現象有:
Object.assign()
Array.prototype.slice()
Array.prototype.concat()
使用擴充套件運運算元實現的複製
Object.assign()
var obj = {
age: 18,
nature: ['smart', 'good'],
names: {
name1: 'fx',
name2: 'xka'
},
love: function () {
console.log('fx is a great girl')
}
}
var newObj = Object.assign({}, fxObj)
slice()
const fxArr = ['One', 'Two', 'Three']
const fxArrs = fxArr.slice(0)
fxArrs[1] = 'love'
consloe.log(fxArr) // ['One', 'Two', 'Three']
consloe.log(fxArrs) // ['One', 'love', 'Three']
concat()
const fxArr = ['One', 'Two', 'Three']
const fxArrs = fxArr.concat()
fxArrs[1] = 'love'
consloe.log(fxArr) // ['One', 'Two', 'Three']
consloe.log(fxArrs) // ['One', 'love', 'Three']
擴充套件運運算元
const fxArr = ['One', 'Two', 'Three']
const fxArrs = [...fxArr]
fxArrs[1] = 'love'
consloe.log(fxArr) // ['One', 'Two', 'Three']
consloe.log(fxArrs) // ['One', 'love', 'Three']
深拷貝開闢一個新的棧,兩個物件相同,但是對應兩個不同的地址,修改一個物件的屬性,不會改變另一個物件的屬性。
常見的深拷貝方式有:
_.cloneDeep()
jQuery.extend()
JSON.stringify()
迴圈遞迴
_.cloneDeep()
const _ = require('lodash')
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: { 1, 2, 3 }
}
const obj2 = _.cloneDeep(obj1)
console.log(obj1.b.f === obj2.b.f) // false
jQuery.extend()
const $ = require('jquery')
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: { 1, 2, 3 }
}
const obj2 = $.extend(true, {}, obj1)
console.log(obj1.b.f === obj2.b.f) // false
JSON.stringify()
const obj = {
name: 'A',
name1: 'undefined',
name2: function () {},
name3: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj1)) // 會忽略undefined、Symbol、函數
console.log(obj2) // { name: 'A' }
迴圈遞迴
function deepClone (obj, hash = new WeakMap()) {
if (obj === null) return obj // 如果是null或者undefined,就不進行拷貝操作
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
// 可能是物件或者普通的值,如果是函數的話不需要深拷貝
if (typeof obj !== 'Object') return obj
// 如果是物件,就進行深拷貝
if (hash.get(obj)) return hash.get(obj)
let cloneObj = new obj.constructor()
// 找到的是所屬型別原型上的constructor,而原型上的constructor指向的是當前類本身
hash.set(obj, cloneObj)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 實現一個遞迴拷貝
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}
淺拷貝只複製記憶體地址,而不復制物件本身,新舊物件還是共用同一塊記憶體,修改物件屬性會影響原物件。
// 淺拷貝
const obj1 = {
name: 'init',
arr: [1, [2, 3], 4]
}
const obj3 = shallowClone(obj1) // 一個淺拷貝方法
obj3.name = 'update‘
obj3.arr[1] = [5, 6, 7] // 新舊物件還是共用同一塊記憶體
console.log('obj1', obj1) // obj1 { name: 'init', arr: [1, [5, 6, 7], 4] }
console.log('obj3', obj3) // obj3 { name: 'update', arr: [1, [5, 6, 7], 4] }
深拷貝會另外創造一個一模一樣的物件,新物件與原物件不共用記憶體,修改新物件不會改到原物件。
// 深拷貝
const obj1 = {
name: 'init',
arr: [1, [2, 3], 4]
}
const obj4 = deepClone(obj1) // 一個深拷貝方法
obj4.name = 'update‘
obj4.arr[1] = [5, 6, 7] // 新物件與原物件不共用記憶體
console.log('obj1', obj1) // obj1 { name: 'init', arr: [1, [2, 3], 4] }
console.log('obj4', obj4) // obj4 { name: 'update', arr: [1, [5, 6, 7], 4] }
當拷貝型別為參照型別時: