浅拷贝

对于复制引用数据类型,只会复制数据最外面一层

  1. function shallowCopy(originObj){
  2. let obj = {}
  3. for(let i in originObj){
  4. obj[i] = originObj[i]
  5. }
  6. return obj
  7. }

被复制对象

  1. let my = {
  2. name:'gromy',
  3. age:18,
  4. study:{
  5. gaozhong:{
  6. wl:'理科',
  7. score:481
  8. },
  9. daxue:{
  10. xuexiao:{
  11. name:'江苏理工',
  12. zhuanye:'计算机'
  13. },
  14. time:'4'
  15. }
  16. }
  17. }

执行

  1. let he = shallowCopy(my)
  2. my.study.gaozhong.score=321
  3. 321
  4. he.study.gaozhong.score
  5. 321

浅复制只复制了最外面一层,所以一层以上的引用还是同一个,使用Object.assign() 也是跟以上相同实现的浅复制。

深拷贝

使用 JSON.stringtify() 实现的深拷贝

问题


    1. var obj={
    2. name:'gromy',
    3. age:NaN,
    4. say:function(){
    5. console.log('我会说话哦!');
    6. },
    7. id:Symbol('id'),
    8. birthday:new Date('1992/01/01')
    9. };
    1. var obj2=JSON.parse(JSON.stringify(obj));
    2. console.log(obj2);
    image.png
  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略
  • Date 日期调用了 toJSON() 将其转换为了 string 字符串(Date.toISOString()),因此会被当做字符串处理。
  • NaN 和 Infinity 格式的数值及 null 都会被当做 null。
  • 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
  • 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

    完整实现

    实现过程

  1. 判断源目标的数据类型
  2. 对不同数据类型进行不同的复制
  3. 递归调用

    深拷贝的痛点-循环引用的问题

    一般我们拷贝的过程是一边遍历一边拷贝
    1. var obj = {};
    2. var b = {obj};
    3. obj.b = b
    就会存在拷贝结束后新的对象中还存在对原来对象的引用
    解决这个问题的方案是,在拷贝的过程中,开一个暂存区,存储已经拷贝的引用,如果需要引用之前的内容,去引用新对象的地址。
    lodash 对此的解决方案
    1. // Check for circular references and return its corresponding clone.
    2. stack || (stack = new Stack)
    3. const stacked = stack.get(value)
    4. if (stacked) {
    5. return stacked
    6. }
    7. stack.set(value, result)
    lodash 是用一个栈去存储所有已经拷贝的引用,每次引用之前去查询是否之前已经存在引用
    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]’

  1. <a name="OhHip"></a>
  2. #### 简单实现
  3. ```javascript
  4. function deepClone(value){
  5. let cache = {}; //暂存已经复制的引用
  6. }

获取引用数据类型

  1. function getTypeName(value){
  2. return Object.prototype.toString.call(value)
  3. }
  1. //判断是否是基础类型的数据
  2. function isPrimitive(value){
  3. return (typeof value === 'string' ||
  4. typeof value === 'number' ||
  5. typeof value === 'symbol' ||
  6. typeof value === 'boolean'||
  7. value === undefined ||
  8. value === null ||
  9. value === NaN ||
  10. value === Infinity)
  11. }

根据已有数据创建新对象

  1. function creatByTag(value){
  2. let cont = value.constructor
  3. return new cont()
  4. }

细分类型

object

  1. if(getTypeName(value) === objectTag){
  2. result = {...value}
  3. Reflect.ownKeys(result).forEach((key)=>{
  4. console.log(result[key])
  5. // if(cache[result[key]]){
  6. // result[key] = cache[result[key]]
  7. // }else{
  8. result[key] = baseClone(result[key])
  9. // cache[result[key]] = result[key]
  10. //}
  11. })
  12. return result
  13. }

Array

  1. //array
  2. if(getTypeName(value) === arrayTag){
  3. result = [...value]
  4. result.forEach((item,i)=>{
  5. result[i] = baseClone(result[i])
  6. })
  7. return result
  8. }

map

  1. //Map
  2. if(getTypeName(value) === mapTag){
  3. result = creatByTag(value)
  4. value.forEach((subValue, key) => {
  5. result.set(key, baseClone(subValue))
  6. })
  7. return result
  8. }

Set 同理

正则

  1. function cloneRegExp(regexp) {
  2. const result = new regexp.constructor(regexp.source, reFlags.exec(regexp))
  3. result.lastIndex = regexp.lastIndex
  4. return result
  5. }

symbol

  1. function cloneSymbol(symbol) {
  2. return Object(Symbol.prototype.valueOf.call(symbol))
  3. }

date

  1. function cloneDate(date) {
  2. let cont = date.constructor
  3. return cont(+date)
  4. }

function

  1. function copyFunc(value){
  2. return eval(`(${value.toString()})`)
  3. }

test

  1. let func = function(){
  2. let a = 2
  3. return function(){
  4. return a++
  5. }
  6. }
  7. let f2 = new Function(func)
  8. f2.prototype == func.prototype //false

包装基础数据类型

  1. '[object Boolean]'
  2. '[object Number]'
  3. '[object Strung]'
  1. function cloneBaseType(value) {
  2. let cont = value.constructor
  3. return cont(value)
  4. }

总代码以及测试

  1. const argsTag = '[object Arguments]'
  2. const arrayTag = '[object Array]'
  3. const boolTag = '[object Boolean]'
  4. const dateTag = '[object Date]'
  5. const errorTag = '[object Error]'
  6. const mapTag = '[object Map]'
  7. const numberTag = '[object Number]'
  8. const objectTag = '[object Object]'
  9. const regexpTag = '[object RegExp]'
  10. const setTag = '[object Set]'
  11. const stringTag = '[object String]'
  12. const symbolTag = '[object Symbol]'
  13. const weakMapTag = '[object WeakMap]'
  14. function deepClone(obj){
  15. let cache = {} //暂存已经复制过的引用
  16. //判断类型
  17. function baseClone(value){
  18. let result
  19. if(isPrimitive(value)){
  20. return value
  21. }
  22. //object
  23. if(getTypeName(value) === objectTag){
  24. result = {...value}
  25. Reflect.ownKeys(result).forEach((key)=>{
  26. console.log(result[key])
  27. // if(cache[result[key]]){
  28. // result[key] = cache[result[key]]
  29. // }else{
  30. result[key] = baseClone(result[key])
  31. // cache[result[key]] = result[key]
  32. //}
  33. })
  34. return result
  35. }
  36. //array
  37. if(getTypeName(value) === arrayTag){
  38. result = [...value]
  39. result.forEach((item,i)=>{
  40. result[i] = baseClone(result[i])
  41. })
  42. return result
  43. }
  44. //Map
  45. if(getTypeName(value) === mapTag){
  46. result = creatByTag(value)
  47. value.forEach((subValue, key) => {
  48. result.set(key, baseClone(subValue))
  49. })
  50. return result
  51. }
  52. }
  53. return baseClone(obj)
  54. }
  55. function getTypeName(value){
  56. return Object.prototype.toString.call(value)
  57. }
  58. //判断是否是基础类型的数据
  59. function isPrimitive(value){
  60. return (typeof value === 'string' ||
  61. typeof value === 'number' ||
  62. typeof value === 'symbol' ||
  63. typeof value === 'boolean'||
  64. value === undefined ||
  65. value === null ||
  66. value === NaN ||
  67. value === Infinity)
  68. }
  69. function creatByTag(value){
  70. let cont = value.constructor
  71. return new cont()
  72. }
  73. let obj = {
  74. name:'gromy',
  75. xuexiao:{
  76. gaozhong:{
  77. name:'第一中学',
  78. time:4
  79. }
  80. },
  81. family:[
  82. {husband:{name:'super man'}},
  83. {son:{name:'super baby',age:1}}
  84. ]
  85. }
  86. let obj2 = deepClone(obj)
  87. console.log('obj2',obj2)
  88. obj.xuexiao.gaozhong.time = 5
  89. obj.family[1].son.age = 0
  90. console.log('obj2',obj2)

lodash 源码

  1. /** `Object#toString` result references. */
  2. const argsTag = '[object Arguments]'
  3. const arrayTag = '[object Array]'
  4. const boolTag = '[object Boolean]'
  5. const dateTag = '[object Date]'
  6. const errorTag = '[object Error]'
  7. const mapTag = '[object Map]'
  8. const numberTag = '[object Number]'
  9. const objectTag = '[object Object]'
  10. const regexpTag = '[object RegExp]'
  11. const setTag = '[object Set]'
  12. const stringTag = '[object String]'
  13. const symbolTag = '[object Symbol]'
  14. const weakMapTag = '[object WeakMap]'
  15. const arrayBufferTag = '[object ArrayBuffer]'
  16. const dataViewTag = '[object DataView]'
  17. const float32Tag = '[object Float32Array]'
  18. const float64Tag = '[object Float64Array]'
  19. const int8Tag = '[object Int8Array]'
  20. const int16Tag = '[object Int16Array]'
  21. const int32Tag = '[object Int32Array]'
  22. const uint8Tag = '[object Uint8Array]'
  23. const uint8ClampedTag = '[object Uint8ClampedArray]'
  24. const uint16Tag = '[object Uint16Array]'
  25. const uint32Tag = '[object Uint32Array]'
  26. /** Used to identify `toStringTag` values supported by `clone`. */
  27. const cloneableTags = {}
  28. cloneableTags[argsTag] = cloneableTags[arrayTag] =
  29. cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
  30. cloneableTags[boolTag] = cloneableTags[dateTag] =
  31. cloneableTags[float32Tag] = cloneableTags[float64Tag] =
  32. cloneableTags[int8Tag] = cloneableTags[int16Tag] =
  33. cloneableTags[int32Tag] = cloneableTags[mapTag] =
  34. cloneableTags[numberTag] = cloneableTags[objectTag] =
  35. cloneableTags[regexpTag] = cloneableTags[setTag] =
  36. cloneableTags[stringTag] = cloneableTags[symbolTag] =
  37. cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
  38. cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true
  39. cloneableTags[errorTag] = cloneableTags[weakMapTag] = false
  40. /** Used to check objects for own properties. */
  41. const hasOwnProperty = Object.prototype.hasOwnProperty
  42. function initCloneByTag(object, tag, isDeep) {
  43. const Ctor = object.constructor
  44. switch (tag) {
  45. case arrayBufferTag:
  46. return cloneArrayBuffer(object)
  47. case boolTag:
  48. case dateTag:
  49. return new Ctor(+object)
  50. case dataViewTag:
  51. return cloneDataView(object, isDeep)
  52. case float32Tag: case float64Tag:
  53. case int8Tag: case int16Tag: case int32Tag:
  54. case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
  55. return cloneTypedArray(object, isDeep)
  56. case mapTag:
  57. return new Ctor
  58. case numberTag:
  59. case stringTag:
  60. return new Ctor(object)
  61. case regexpTag:
  62. return cloneRegExp(object)
  63. case setTag:
  64. return new Ctor
  65. case symbolTag:
  66. return cloneSymbol(object)
  67. }
  68. }
  69. /**
  70. * Initializes an array clone.
  71. *
  72. * @private
  73. * @param {Array} array The array to clone.
  74. * @returns {Array} Returns the initialized clone.
  75. */
  76. function initCloneArray(array) {
  77. const { length } = array
  78. const result = new array.constructor(length)
  79. // Add properties assigned by `RegExp#exec`.
  80. if (length && typeof array[0] === 'string' && hasOwnProperty.call(array, 'index')) {
  81. result.index = array.index
  82. result.input = array.input
  83. }
  84. return result
  85. }
  86. /**
  87. * The base implementation of `clone` and `cloneDeep` which tracks
  88. * traversed objects.
  89. *
  90. * @private
  91. * @param {*} value The value to clone.
  92. * @param {number} bitmask The bitmask flags.
  93. * 1 - Deep clone
  94. * 2 - Flatten inherited properties
  95. * 4 - Clone symbols
  96. * @param {Function} [customizer] The function to customize cloning.
  97. * @param {string} [key] The key of `value`.
  98. * @param {Object} [object] The parent object of `value`.
  99. * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
  100. * @returns {*} Returns the cloned value.
  101. */
  102. function baseClone(value, bitmask, customizer, key, object, stack) {
  103. let result
  104. const isDeep = bitmask & CLONE_DEEP_FLAG
  105. const isFlat = bitmask & CLONE_FLAT_FLAG
  106. const isFull = bitmask & CLONE_SYMBOLS_FLAG
  107. if (customizer) {
  108. result = object ? customizer(value, key, object, stack) : customizer(value)
  109. }
  110. if (result !== undefined) {
  111. return result
  112. }
  113. if (!isObject(value)) {
  114. return value
  115. }
  116. const isArr = Array.isArray(value)
  117. const tag = getTag(value)
  118. if (isArr) {
  119. result = initCloneArray(value)
  120. if (!isDeep) {
  121. return copyArray(value, result)
  122. }
  123. } else {
  124. const isFunc = typeof value === 'function'
  125. if (isBuffer(value)) {
  126. return cloneBuffer(value, isDeep)
  127. }
  128. if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
  129. result = (isFlat || isFunc) ? {} : initCloneObject(value)
  130. if (!isDeep) {
  131. return isFlat
  132. ? copySymbolsIn(value, copyObject(value, keysIn(value), result))
  133. : copySymbols(value, Object.assign(result, value))
  134. }
  135. } else {
  136. if (isFunc || !cloneableTags[tag]) {
  137. return object ? value : {}
  138. }
  139. result = initCloneByTag(value, tag, isDeep)
  140. }
  141. }
  142. // Check for circular references and return its corresponding clone.
  143. stack || (stack = new Stack)
  144. const stacked = stack.get(value)
  145. if (stacked) {
  146. return stacked
  147. }
  148. stack.set(value, result)
  149. if (tag == mapTag) {
  150. value.forEach((subValue, key) => {
  151. result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
  152. })
  153. return result
  154. }
  155. if (tag == setTag) {
  156. value.forEach((subValue) => {
  157. result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
  158. })
  159. return result
  160. }
  161. if (isTypedArray(value)) {
  162. return result
  163. }
  164. const keysFunc = isFull
  165. ? (isFlat ? getAllKeysIn : getAllKeys)
  166. : (isFlat ? keysIn : keys)
  167. const props = isArr ? undefined : keysFunc(value)
  168. arrayEach(props || value, (subValue, key) => {
  169. if (props) {
  170. key = subValue
  171. subValue = value[key]
  172. }
  173. // Recursively populate clone (susceptible to call stack limits).
  174. assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
  175. })
  176. return result
  177. }
  178. export default baseClone