JavaScript 中,数据类型分为两大类型,基本类型和引用类型。
简述一下,基本类型是指我们常见的Undefined、Null、String、Number、Boolean,是保存在 栈内存 中的简单数据段;而引用类型则是指Object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)。
先来了解一下最常见的Object和Array,作为最常用引用类型,Object本身是个键值对的集合,组成一个拥有多属性的数据。Array则是个有序的数据列表。两者都能储存各种基本类型数据。
在JavaScript声明变量的过程中,对Object和Array的声明实际上并不是给变量赋值,而是类似于C语言中的指针,将变量指向于 堆内存 中,通过变量名寻址找到相应的对象。当没有任何变量指向于该对象时,现代浏览器会进行销毁回收内存空间。
所以在对JavaScript了解不够的时候,经常会对引用类型进行类似于以下的操作:
var a = { key: 'test' }
var b = a
// 更改key的值
b.key = 1
这种操作会直接导致两个变量引用的对象值发生变化,当重新访问 a.key 的,实际拿到的就是 1 了,这个就是我们常说的浅复制。
那么怎么操作才能复制好一个对象出来呢?不妨思考一下,上文可以得知,对象和数组的组成其实就是我们常用的基本类型或引用类型组成的,要复制他们,其实就是创建出相同的数据结构,把对应的值赋上去,,而引用类型中包含引用类型,则也可以执行上述操作,直到复制出两个对象。
以下用递归实现一下原理。
/**
* 深度复制朴素对象和数组
* 除此之外都做浅复制处理
* @author chen-rongliang@qq.com
* @param obejcts
* 待复制变量
* @return 合并的对象
* @example
*
* var extend = require('./extend')
* var y = {
* a: 1,
* b: '2',
* c: { d: [3] }
* },
* z = {
* a: 2,
* e: 6,
* f: {
* g: 7
* }
* }
*
* var x = extend(y, z)
*
* x.c.d = 999
* z.f.g = 8
*
*
*/
const Extend = (obj, temp) => {
// 类型
let typeStr = obj.constructor.name
// 类型为朴素对象或数组
if (typeStr === 'Object' || typeStr === 'Array') {
// 副本初始化类型
if (temp === undefined) {
temp = obj instanceof Array ? [] : {}
}
// 遍历
for (let key in obj) {
// 如果遍历子也是复杂类型,递归调用
if (obj[key] instanceof Object) {
temp[key] = Extend(obj[key])
} else {
// 直接赋值
temp[key] = obj[key]
}
}
} else {
// 简单类型 / 非数组 / 非朴素对象 直接复制
temp = obj
}
return temp
}
module.exports = (...objs) => {
let concats = {}
for (let obj of objs) {
Extend(obj, concats)
}
return concats
}
除了这种方法,代码实现还有各种各样的,这个就不一一介绍了。