JavaScript中存在三种可执行代码,global,function,eval。可执行代码的执行依赖于执行上下文。咱们能够抽象的将执行上下文理解为一个对象。这个对象上会包含一些属性,如variable object(变量对象),this value(this指针),scope chain(做用域链)。前端
JavaScript中的基础数据类型都是存储在变量对象中。JavaScript中共有5种基础数据类型,Undefined、Null、Boolean、Number、String。咱们能够直接操做存储在变量对象中的,基础数据类型。git
JavaScript中的引用数据类型的值,是保存在堆内存中的。咱们不能直接操做堆内存空间。咱们经过操做variable object中的数据的引用,访问修改对象。这里的应用是堆内存空间中的地址。github
当咱们使用 var a = b赋值的时候,若是b是引用类型,咱们赋于a的只是内存空间的地址。正则表达式
这就是产生了一个问题,咱们该如何复制引用类型的对象。数据结构
最简单的方式将对象使用JSON.stringify将对象序列化为JSON格式的字符串,而后使用JSON.parse将JSON字符串反序列化为对象。app
另一种方式是使用for in循环,配合递归函数
到这里,问题解决了吗?并无,上面方式有如下的问题ui
对于上面问题的解决, 我参考了lodash的源码,以及ramda的源码。lodash的源码中涉及到很是多的边界条件的处理,可读性相对差一些。我在此基础上作出了必定的简化,具体代码在文末。this
当咱们检测到value的为[object Date]类型的时候,咱们经过Date实例的constructor属性,获取实例的构造函数。Date的构造函数能够接收一个Date的实例做为参数。从而建立一个新的Date实例。spa
和Date对象同理,使用Map或者Set实例的constructor属性,建立新的实例后,经过forEach对Map和Set进行遍历,从新设置Map以及Set的内容。
当出现循环引用的时候,若是不进行判断,使用递归。会爆栈
WeakMap支持使用引用类型做为key,咱们能够利用这个特性,将对象的每个引用类型的属性做为key,存储在WeakMap中。当一样的key再次出现时,能够证实发生了循环引用。直接返回结果。
Symbols类型的属性没法使用Object.keys获取,能够经过Object.getOwnPropertySymbols方法获取对象上的Symbol类型的属性。
// 使用WeakMap判断是否造成了环,避免爆栈
const hash = new WeakMap()
function hasHash (value) {
return hash.has(value)
}
function setHash (value, result) {
hash.set(value, result)
}
function getHash (value) {
return hash.get(value)
}
function getSymbols (value) {
let symKeys = []
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable
const nativeGetSymbols = Object.getOwnPropertySymbols
symKeys = nativeGetSymbols(value)
// 判断是不是可枚举的属性
symKeys = symKeys.filter(symkey => propertyIsEnumerable.call(value, symkey))
return symKeys
}
function getAllKeys (value) {
let keys = Object.keys(value)
keys = [...keys, ...getSymbols(value)]
return keys
}
const types = {
'[object Array]': true,
'[object Boolean]': true,
'[object Date]': true,
'[object Map]': true,
'[object Set]': true,
'[object Number]': true,
'[object Object]': true,
'[object RegExp]': true,
'[object Symbol]': true,
'[object String]': true,
'[object ArrayBuffer]': true,
'[object Function]': true,
'[object WeakMap]': false,
'[object Error]': false
}
function isObject (value) {
const type = typeof value;
return value != null && (type === 'object' || type === 'function')
}
function getType (value) {
return Object.prototype.toString.call(value);
}
function initCloneArray (value) {
const { length } = value
return new value.constructor(length)
}
function initCloneArrayBuffer (value) {
const result = new value.constructor(value.byteLength)
new Uint8Array(result).set(new Uint8Array(value))
return result
}
function initCloneObject (value) {
return Object.create(Object.getPrototypeOf(value))
}
function initCloneRegExp (value) {
return new RegExp(value.source,
(value.global ? 'g' : '') +
(value.ignoreCase ? 'i' : '') +
(value.multiline ? 'm' : '') +
(value.sticky ? 'y' : '') +
(value.unicode ? 'u' : '')
)
}
function initCloneFunction (value) {
return function (...args) {
return value.apply(null, ...args)
}
}
function initClone (value, type) {
const Ctor = value.constructor
switch (type) {
case '[object ArrayBuffer]':
return initCloneArrayBuffer(value)
case '[object Date]':
case '[object Map]':
case '[object Set]':
return new Ctor(value)
case '[object RegExp]':
return initCloneRegExp(value)
case '[object Array]':
return initCloneArray(value)
case '[object Object]':
return initCloneObject(value)
case '[object Function]':
return initCloneFunction(value)
}
}
export default function deepClone (value) {
let result;
const type = getType(value)
// 若是不是引用类型直接返回
if (!isObject(value)) {
return value
}
// 若是是weakMap,Error类型直接返回
if (!types[type]) {
return value
}
let isArr = Array.isArray(value)
result = initClone(value, type)
// 判断是否产生了循环引用, 避免爆栈
if (hasHash(value)) {
// 若是存在直接返回
return getHash(value)
}
// 在weakMap添加标记
setHash(value, result)
if (type === '[object Map]') {
value.forEach((val, key) => {
result.set(key, deepClone(val))
})
return result
}
if (type === '[object Set]') {
value.forEach((val, key) => {
result.add(deepClone(val))
})
return result
}
const props = getAllKeys(value)
for (let i = 0; i < props.length; i++) {
const key = props[i]
const val = value[key]
result[key] = deepClone(val)
}
return result
}
复制代码