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
}
複製代碼