js存储机制
JS中对象分为基本类型和复合(引用)类型,基本类型存放在栈内存,复合(引用)类型存放在堆内存。
堆内存用于存放由new创建的对象,栈内存存放一些基本类型的变量和对象的引用变量。
1.基本类型
如 变量,在创建时会直接复制一份。
var a = 'hello men'
var b = a
b = "good"
console.log(a) // hello men
console.log(b) // good
当b改变时,不会影响初始变量a
2.复合类型
如数组、对象等,js就会做引用,原对象和拷贝对象会做关联
var arr1 = [1, 2, 3, 4]
var arr2 = arr1
arr2[4] = 9
console.log(arr1) // [1,2,3,4,9]
console.log(arr2) // [1,2,3,4,9]
arr2改变时arr1也随之改变,这里举了数组的例子,对象也类似。
浅与深的区别
为了让拷贝对象 和 原对象完全脱离关系,我们需要用到浅拷贝、深拷贝这两种方法。
浅拷贝只能复制根属性,做不到真正的全拷贝。
深拷贝能实现真正的全拷贝,与原对象分清界限。
浅拷贝
我们先从简单的入手,浅浅的拷贝一个数组。
- 方法1
var array1 = ['a', 'b', 'c']
var array2 = array1.slice() // 单个复制数组元素
array2[3] = 'h'
console.log(array1) // ['a','b','c']
console.log(array2) // ['a','b','c', 'h']
- 方法2
var arr1 = [1, 2, 3]
var arr2 = []
var copy = (arr1, arr2) => {
arr1.forEach((element, index) => {
arr2[index] = element
})
return arr2
}
arr2 = copy(arr1, arr2)
arr2[3] = 6
console.log(arr2) // [ 1, 2, 3, 6 ]
console.log(arr1) // [ 1, 2, 3 ]
array2只改变自己,不影响他人,是个好同志。
现在难度升级,我们在数组里加入了对象。
var array1 = ['a', 'b', 'c', {
"name": 'leo'
}]
var array2 = array1.slice()
array2[3].name = 'mark'
console.log(array1) // ['a','b','c',{name: 'mark'}]
console.log(array2) // ['a','b','c',{name: 'mark'}]
这时候改变array2,array1也跟着变了,哦NO!
深拷贝
现在是该大英雄出场了,“深拷贝!深拷贝!”
不啰嗦,直接上代码。
// 创建了 一个带数组和对象的元素 (这还是比较简单的对象结构)
var array1 = ['a', 'b', 'c', { "name": 'leo'}, [8, 9]]
var array2 = []
// 创建拷贝方法
var copy = (obj1, obj2) => {
obj1.forEach((item, index) => {
// 判断该索引值是否为数组
if (obj1[index].constructor === Array) {
obj2[index] = []
obj1[index].forEach((subItem, subIndex) => {
obj2[index][subIndex] = subItem
})
// 判断该索引值是否为对象
} else if (obj1[index] && typeof obj1[index] === 'object') {
obj2[index] = {}
for (let element in obj1[index]) {
obj2[index][element] = obj1[index][element]
}
// 不是对象,说明是属性,直接赋值
} else {
obj2[index] = item
}
})
return array2
}
var array2 = copy(array1, array2)
// 改变对象的属性值
array2[3].name = 'mark'
// 改变数组的值
array2[4][1] = '5'
console.log(array2) // [ 'a', 'b', 'c', { name: 'mark' }, [ 8, '5' ] ]
console.log(array1) // [ 'a', 'b', 'c', { name: 'leo' }, [ 8, 9 ] ]
恭喜你,我们向要的都实现了!
深不可测的深拷贝 (递归)
有些对象是后台传给我们的,拿到之前不知道里面是什么结构。这种数据着么去拷贝呢?
是时候展示真正的技术了, 那就是递归。
递,层层递进。归,归去来兮。
总结下来就是...还是不解释了。
看代码:
// 创建一个深层嵌套的对象
var json1 = {
"name": "leo",
"age": 20,
"child": [{
"eye": "blue",
"child": [{
"photo": "nice"
}, {
"photo": "beautiful"
}]
},
{
"eye": "red",
"child": []
}
]
}
var json2 = {}
function copy(obj1, obj2) {
for (var name in obj1) {
if (typeof obj1[name] === "object") {
obj2[name] = (obj1[name].constructor === Array) ? [] : {}
// 递归时改变当前参数位置,
// 举例:当前name为child时,copy中的参数被替换为 (obj1.child, obj2.child)
copy(obj1[name], obj2[name])
} else {
obj2[name] = obj1[name]
}
}
return obj2
}
json2 = copy(json1, json2)
json2.child[0].eye = 'green'
console.log(json1) // child: [ { eye: 'blue', child: [Object] }
console.log(json2) // child: [ { eye: 'green', child: [Object] }
最简单的深拷贝
先把对象使用JSON.stringify()转为字符串,再赋值给另外一个变量,然后使用JSON.parse()转回来即可。
let a = {
a1: 1,
a2: '2',
a3: [1, 2, 3, 4, 5, 6],
a4: {
deep1: 1,
deep2: 2
}
}
let b = JSON.parse(JSON.stringify(a))
console.log(b)
a.a4.deep1 = 99
console.log(a) // a的属性值变动了
console.log(b) // b没变
new
顺带提下,用new 创建的对象,都是引用原对象的。new常常和构造函数一起出现,用于创建对象的继承关系。
我们应尽量避免引用类型的直接拷贝,这样会改变原对象的属性,在协同工作时会产生不可预期的错误。
可以用新的方法Object.create()来创建,或者定义一个空的 F(){} 构造函数做衔接。