浅拷贝
对于复制引用数据类型,只会复制数据最外面一层
function shallowCopy(originObj){let obj = {}for(let i in originObj){obj[i] = originObj[i]}return obj}
被复制对象
let my = {name:'gromy',age:18,study:{gaozhong:{wl:'理科',score:481},daxue:{xuexiao:{name:'江苏理工',zhuanye:'计算机'},time:'4'}}}
执行
let he = shallowCopy(my)my.study.gaozhong.score=321321he.study.gaozhong.score321
浅复制只复制了最外面一层,所以一层以上的引用还是同一个,使用Object.assign() 也是跟以上相同实现的浅复制。
深拷贝
使用 JSON.stringtify() 实现的深拷贝
问题
var obj={name:'gromy',age:NaN,say:function(){console.log('我会说话哦!');},id:Symbol('id'),birthday:new Date('1992/01/01')};
var obj2=JSON.parse(JSON.stringify(obj));console.log(obj2);

- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略
- Date 日期调用了 toJSON() 将其转换为了 string 字符串(Date.toISOString()),因此会被当做字符串处理。
- NaN 和 Infinity 格式的数值及 null 都会被当做 null。
- 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
完整实现
实现过程
- 判断源目标的数据类型
- 对不同数据类型进行不同的复制
- 递归调用
深拷贝的痛点-循环引用的问题
一般我们拷贝的过程是一边遍历一边拷贝
就会存在拷贝结束后新的对象中还存在对原来对象的引用var obj = {};var b = {obj};obj.b = b
解决这个问题的方案是,在拷贝的过程中,开一个暂存区,存储已经拷贝的引用,如果需要引用之前的内容,去引用新对象的地址。
lodash 对此的解决方案
lodash 是用一个栈去存储所有已经拷贝的引用,每次引用之前去查询是否之前已经存在引用// Check for circular references and return its corresponding clone.stack || (stack = new Stack)const stacked = stack.get(value)if (stacked) {return stacked}stack.set(value, result)
lodash 对于类型的处理非常的全面``javascript /**Object#toString` result references. */ const argsTag = ‘[object Arguments]’ const arrayTag = ‘[object Array]’ const boolTag = ‘[object Boolean]’ const dateTag = ‘[object Date]’ const errorTag = ‘[object Error]’ const mapTag = ‘[object Map]’ const numberTag = ‘[object Number]’ const objectTag = ‘[object Object]’ const regexpTag = ‘[object RegExp]’ const setTag = ‘[object Set]’ const stringTag = ‘[object String]’ const symbolTag = ‘[object Symbol]’ const weakMapTag = ‘[object WeakMap]’
const arrayBufferTag = ‘[object ArrayBuffer]’ const dataViewTag = ‘[object DataView]’ const float32Tag = ‘[object Float32Array]’ const float64Tag = ‘[object Float64Array]’ const int8Tag = ‘[object Int8Array]’ const int16Tag = ‘[object Int16Array]’ const int32Tag = ‘[object Int32Array]’ const uint8Tag = ‘[object Uint8Array]’ const uint8ClampedTag = ‘[object Uint8ClampedArray]’ const uint16Tag = ‘[object Uint16Array]’ const uint32Tag = ‘[object Uint32Array]’
<a name="OhHip"></a>#### 简单实现```javascriptfunction deepClone(value){let cache = {}; //暂存已经复制的引用}
获取引用数据类型
function getTypeName(value){return Object.prototype.toString.call(value)}
//判断是否是基础类型的数据function isPrimitive(value){return (typeof value === 'string' ||typeof value === 'number' ||typeof value === 'symbol' ||typeof value === 'boolean'||value === undefined ||value === null ||value === NaN ||value === Infinity)}
根据已有数据创建新对象
function creatByTag(value){let cont = value.constructorreturn new cont()}
object
if(getTypeName(value) === objectTag){result = {...value}Reflect.ownKeys(result).forEach((key)=>{console.log(result[key])// if(cache[result[key]]){// result[key] = cache[result[key]]// }else{result[key] = baseClone(result[key])// cache[result[key]] = result[key]//}})return result}
Array
//arrayif(getTypeName(value) === arrayTag){result = [...value]result.forEach((item,i)=>{result[i] = baseClone(result[i])})return result}
map
//Mapif(getTypeName(value) === mapTag){result = creatByTag(value)value.forEach((subValue, key) => {result.set(key, baseClone(subValue))})return result}
正则
function cloneRegExp(regexp) {const result = new regexp.constructor(regexp.source, reFlags.exec(regexp))result.lastIndex = regexp.lastIndexreturn result}
symbol
function cloneSymbol(symbol) {return Object(Symbol.prototype.valueOf.call(symbol))}
date
function cloneDate(date) {let cont = date.constructorreturn cont(+date)}
function
function copyFunc(value){return eval(`(${value.toString()})`)}
test
let func = function(){let a = 2return function(){return a++}}let f2 = new Function(func)f2.prototype == func.prototype //false
包装基础数据类型
'[object Boolean]''[object Number]''[object Strung]'
function cloneBaseType(value) {let cont = value.constructorreturn cont(value)}
总代码以及测试
const argsTag = '[object Arguments]'const arrayTag = '[object Array]'const boolTag = '[object Boolean]'const dateTag = '[object Date]'const errorTag = '[object Error]'const mapTag = '[object Map]'const numberTag = '[object Number]'const objectTag = '[object Object]'const regexpTag = '[object RegExp]'const setTag = '[object Set]'const stringTag = '[object String]'const symbolTag = '[object Symbol]'const weakMapTag = '[object WeakMap]'function deepClone(obj){let cache = {} //暂存已经复制过的引用//判断类型function baseClone(value){let resultif(isPrimitive(value)){return value}//objectif(getTypeName(value) === objectTag){result = {...value}Reflect.ownKeys(result).forEach((key)=>{console.log(result[key])// if(cache[result[key]]){// result[key] = cache[result[key]]// }else{result[key] = baseClone(result[key])// cache[result[key]] = result[key]//}})return result}//arrayif(getTypeName(value) === arrayTag){result = [...value]result.forEach((item,i)=>{result[i] = baseClone(result[i])})return result}//Mapif(getTypeName(value) === mapTag){result = creatByTag(value)value.forEach((subValue, key) => {result.set(key, baseClone(subValue))})return result}}return baseClone(obj)}function getTypeName(value){return Object.prototype.toString.call(value)}//判断是否是基础类型的数据function isPrimitive(value){return (typeof value === 'string' ||typeof value === 'number' ||typeof value === 'symbol' ||typeof value === 'boolean'||value === undefined ||value === null ||value === NaN ||value === Infinity)}function creatByTag(value){let cont = value.constructorreturn new cont()}let obj = {name:'gromy',xuexiao:{gaozhong:{name:'第一中学',time:4}},family:[{husband:{name:'super man'}},{son:{name:'super baby',age:1}}]}let obj2 = deepClone(obj)console.log('obj2',obj2)obj.xuexiao.gaozhong.time = 5obj.family[1].son.age = 0console.log('obj2',obj2)
lodash 源码
/** `Object#toString` result references. */const argsTag = '[object Arguments]'const arrayTag = '[object Array]'const boolTag = '[object Boolean]'const dateTag = '[object Date]'const errorTag = '[object Error]'const mapTag = '[object Map]'const numberTag = '[object Number]'const objectTag = '[object Object]'const regexpTag = '[object RegExp]'const setTag = '[object Set]'const stringTag = '[object String]'const symbolTag = '[object Symbol]'const weakMapTag = '[object WeakMap]'const arrayBufferTag = '[object ArrayBuffer]'const dataViewTag = '[object DataView]'const float32Tag = '[object Float32Array]'const float64Tag = '[object Float64Array]'const int8Tag = '[object Int8Array]'const int16Tag = '[object Int16Array]'const int32Tag = '[object Int32Array]'const uint8Tag = '[object Uint8Array]'const uint8ClampedTag = '[object Uint8ClampedArray]'const uint16Tag = '[object Uint16Array]'const uint32Tag = '[object Uint32Array]'/** Used to identify `toStringTag` values supported by `clone`. */const cloneableTags = {}cloneableTags[argsTag] = cloneableTags[arrayTag] =cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =cloneableTags[boolTag] = cloneableTags[dateTag] =cloneableTags[float32Tag] = cloneableTags[float64Tag] =cloneableTags[int8Tag] = cloneableTags[int16Tag] =cloneableTags[int32Tag] = cloneableTags[mapTag] =cloneableTags[numberTag] = cloneableTags[objectTag] =cloneableTags[regexpTag] = cloneableTags[setTag] =cloneableTags[stringTag] = cloneableTags[symbolTag] =cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = truecloneableTags[errorTag] = cloneableTags[weakMapTag] = false/** Used to check objects for own properties. */const hasOwnProperty = Object.prototype.hasOwnPropertyfunction initCloneByTag(object, tag, isDeep) {const Ctor = object.constructorswitch (tag) {case arrayBufferTag:return cloneArrayBuffer(object)case boolTag:case dateTag:return new Ctor(+object)case dataViewTag:return cloneDataView(object, isDeep)case float32Tag: case float64Tag:case int8Tag: case int16Tag: case int32Tag:case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:return cloneTypedArray(object, isDeep)case mapTag:return new Ctorcase numberTag:case stringTag:return new Ctor(object)case regexpTag:return cloneRegExp(object)case setTag:return new Ctorcase symbolTag:return cloneSymbol(object)}}/*** Initializes an array clone.** @private* @param {Array} array The array to clone.* @returns {Array} Returns the initialized clone.*/function initCloneArray(array) {const { length } = arrayconst result = new array.constructor(length)// Add properties assigned by `RegExp#exec`.if (length && typeof array[0] === 'string' && hasOwnProperty.call(array, 'index')) {result.index = array.indexresult.input = array.input}return result}/*** The base implementation of `clone` and `cloneDeep` which tracks* traversed objects.** @private* @param {*} value The value to clone.* @param {number} bitmask The bitmask flags.* 1 - Deep clone* 2 - Flatten inherited properties* 4 - Clone symbols* @param {Function} [customizer] The function to customize cloning.* @param {string} [key] The key of `value`.* @param {Object} [object] The parent object of `value`.* @param {Object} [stack] Tracks traversed objects and their clone counterparts.* @returns {*} Returns the cloned value.*/function baseClone(value, bitmask, customizer, key, object, stack) {let resultconst isDeep = bitmask & CLONE_DEEP_FLAGconst isFlat = bitmask & CLONE_FLAT_FLAGconst isFull = bitmask & CLONE_SYMBOLS_FLAGif (customizer) {result = object ? customizer(value, key, object, stack) : customizer(value)}if (result !== undefined) {return result}if (!isObject(value)) {return value}const isArr = Array.isArray(value)const tag = getTag(value)if (isArr) {result = initCloneArray(value)if (!isDeep) {return copyArray(value, result)}} else {const isFunc = typeof value === 'function'if (isBuffer(value)) {return cloneBuffer(value, isDeep)}if (tag == objectTag || tag == argsTag || (isFunc && !object)) {result = (isFlat || isFunc) ? {} : initCloneObject(value)if (!isDeep) {return isFlat? copySymbolsIn(value, copyObject(value, keysIn(value), result)): copySymbols(value, Object.assign(result, value))}} else {if (isFunc || !cloneableTags[tag]) {return object ? value : {}}result = initCloneByTag(value, tag, isDeep)}}// Check for circular references and return its corresponding clone.stack || (stack = new Stack)const stacked = stack.get(value)if (stacked) {return stacked}stack.set(value, result)if (tag == mapTag) {value.forEach((subValue, key) => {result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))})return result}if (tag == setTag) {value.forEach((subValue) => {result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))})return result}if (isTypedArray(value)) {return result}const keysFunc = isFull? (isFlat ? getAllKeysIn : getAllKeys): (isFlat ? keysIn : keys)const props = isArr ? undefined : keysFunc(value)arrayEach(props || value, (subValue, key) => {if (props) {key = subValuesubValue = value[key]}// Recursively populate clone (susceptible to call stack limits).assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))})return result}export default baseClone
