什么是深拷贝?

  • 简单理解
    • b是a的一份拷贝,b中没有对a中对象的引用
  • 另一种理解

    • b是a的一份拷贝
    • 把a和b各画出图,a与b没有连接

      JSON序列化反序列化

      1. var a = {
      2. b: 1,
      3. c: [1, 2, 3],
      4. d: {
      5. d1: 'ddd1',
      6. d2: 'ddd2'
      7. }
      8. }
      9. var a2 = JSON.parse(JSON.stringify(a))

      缺点

  • 不支持undefined

  • 不支持function
  • 不支持Date
  • 不支持正则
  • 不支持symbol
  • 不支持引用,循环引用

递归克隆

思路

  • 递归
    • 看节点类型(7种)
    • 如果是基本类型就直接拷贝
    • 如果是object就分情况讨论
  • object分为

    • 普通object - for in(有bug)?
    • 数组array - Array初始化?
    • 函数function - 怎么拷贝?闭包?
    • 日期 Date - 怎么拷贝?

      简单配置

  • $ yarn init -y

  • $ yarn add -D chai mocha sinon sinon-chai
  • package.json
    • "scripts": {"test": "mocha test/**/*.js"}

/src/index.js

  1. function deepClone() {
  2. }
  3. module.exports = deepClone

/test/index.js

  1. const chai = require("chai");
  2. const sinon = require("sinon");
  3. const sinonChai = require("sinon-chai");
  4. chai.use(sinonChai);
  5. const assert = chai.assert;
  6. const deepClone = require('../src/index.js')
  7. describe('deepClone', () => {
  8. it.only("是一个函数", () => {
  9. assert.isFunction(deepClone)
  10. })
  11. });

$ yarn test
image.png

代码

  • 可以复制普通对象

    1. function deepClone(source) {
    2. if (source instanceof Object) {
    3. const dist = new Object()
    4. for (const key in source) {
    5. dist[key] = deepClone(source[key])
    6. }
    7. return dist
    8. }
    9. return source
    10. }
    11. module.exports = deepClone
  • 可以复制数组

    1. function deepClone(source) {
    2. if (source instanceof Object) {
    3. if (source instanceof Array) {
    4. const dist = new Array()
    5. for (const key in source) {
    6. dist[key] = deepClone(source[key])
    7. }
    8. return dist
    9. } else {
    10. const dist = new Object()
    11. for (const key in source) {
    12. dist[key] = deepClone(source[key])
    13. }
    14. return dist
    15. }
    16. }
    17. return source
    18. }
    19. module.exports = deepClone
  • 可以复制函数

    1. function deepClone(source) {
    2. if (source instanceof Object) {
    3. if (source instanceof Array) {
    4. const dist = new Array()
    5. for (const key in source) {
    6. dist[key] = deepClone(source[key])
    7. }
    8. return dist
    9. } else if (source instanceof Function) {
    10. const dist = function () {
    11. return source.apply(this, arguments)
    12. }
    13. for (const key in source) {
    14. dist[key] = deepClone(source[key])
    15. }
    16. return dist
    17. } else {
    18. const dist = new Object()
    19. for (const key in source) {
    20. dist[key] = deepClone(source[key])
    21. }
    22. return dist
    23. }
    24. }
    25. return source
    26. }
    27. module.exports = deepClone

    测试用例 ```javascript const chai = require(“chai”); const sinon = require(“sinon”); const sinonChai = require(“sinon-chai”); chai.use(sinonChai); const assert = chai.assert; const deepClone = require(‘../src/index.js’) describe(‘deepClone’, () => { it(“是一个函数”, () => {

    1. assert.isFunction(deepClone)

    }) it(‘能够复制基本类型’, () => {

    1. const n = 123
    2. const n2 = deepClone(n)
    3. assert(n === n2)
    4. const s = '123456'
    5. const s2 = deepClone(s)
    6. assert(s === s2)
    7. const b = true
    8. const b2 = deepClone(b)
    9. assert(b === b2)
    10. const u = undefined
    11. const u2 = deepClone(u)
    12. assert(u === u2)
    13. const empty = null
    14. const empty2 = deepClone(empty)
    15. assert(empty === empty2)
    16. const symbol = Symbol()
    17. const symbol2 = deepClone(symbol)
    18. assert(symbol === symbol2)

    }) describe(‘复制对象’, () => {

    1. it('能够复制普通对象', () => {
    2. const a = {
    3. name: "Gouson",
    4. age: 18,
    5. child: {
    6. name: 'Joe'
    7. }
    8. }
    9. const a2 = deepClone(a)
    10. assert(a !== a2)
    11. assert(a.name === a2.name)
    12. assert(a.child !== a2.child)
    13. assert(a.child.name === a2.child.name)
    14. })
    15. it('能够复制数组对象', () => {
    16. const a = [
    17. [11, 12],
    18. [21, 22],
    19. [31, 32]
    20. ]
    21. const a2 = deepClone(a)
    22. assert(a !== a2)
    23. assert(a[0] !== a2[0])
    24. assert(a[1] !== a2[1])
    25. assert(a[2] !== a2[2])
    26. assert.deepEqual(a, a2)
    27. });
    28. it('能够复制函数', () => {
    29. const a = function () {
    30. return 1
    31. }
    32. a.xxx = {
    33. yyy: {
    34. zzz: 1
    35. }
    36. }
    37. const a2 = deepClone(a)
    38. assert(a !== a2)
    39. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
    40. assert(a.xxx.yyy !== a2.xxx.yyy)
    41. assert(a.xxx !== a2.xxx)
    42. assert(a() === a2())
    43. });

    });

});

  1. <a name="lWqhW"></a>
  2. # 解决环引用
  3. ```javascript
  4. let cache = []
  5. function deepClone(source) {
  6. if (source instanceof Object) {
  7. let cacheDist = findCache(source)
  8. if (cacheDist) {
  9. return cacheDist
  10. } else {
  11. let dist
  12. if (source instanceof Array) {
  13. dist = new Array()
  14. } else if (source instanceof Function) {
  15. dist = function () {
  16. return source.apply(this, arguments)
  17. }
  18. } else {
  19. dist = new Object()
  20. }
  21. cache.push([source, dist])
  22. for (const key in source) {
  23. dist[key] = deepClone(source[key])
  24. }
  25. return dist
  26. }
  27. }
  28. return source
  29. }
  30. function findCache(source) {
  31. for (let i = 0; i < cache.length; i++) {
  32. if (cache[i][0] === source) {
  33. return cache[i][1]
  34. }
  35. }
  36. return undefined
  37. }
  38. module.exports = deepClone
  1. it('环也能复制', () => {
  2. const a = {
  3. name: "Gouson"
  4. }
  5. a.self = a
  6. const a2 = deepClone(a)
  7. assert(a !== a2)
  8. assert(a.name === a2.name)
  9. assert(a.self !== a2.self)
  10. })

考虑爆栈

解决办法是用循环来把深度变为长度
一般不考虑

拷贝RegExp

  1. ...
  2. else if (source instanceof RegExp) {
  3. dist = new RegExp(source.source, source.flags)
  4. }
  5. ...
  1. it('可以复制正则', () => {
  2. const a = new RegExp('hi\\d+', 'gi')
  3. a.xxx = {
  4. yyy: {
  5. zzz: '1'
  6. }
  7. }
  8. const a2 = deepClone(a)
  9. assert(a.source === a2.source)
  10. assert(a.flags === a2.flags)
  11. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  12. assert(a.xxx.yyy !== a2.xxx.yyy)
  13. assert(a.xxx !== a2.xxx)
  14. assert(a !== a2)
  15. })

考虑日期

  1. ...
  2. else if (source instanceof Date) {
  3. dist = new Date(source)
  4. }
  5. ...
  1. it('可以复制日期', () => {
  2. const a = new Date()
  3. a.xxx = {
  4. yyy: {
  5. zzz: '1'
  6. }
  7. }
  8. const a2 = deepClone(a)
  9. assert(a.getTime() === a2.getTime())
  10. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  11. assert(a.xxx.yyy !== a2.xxx.yyy)
  12. assert(a.xxx !== a2.xxx)
  13. assert(a !== a2)
  14. })

原型链

原型链上的属性一般不会进行深拷贝

  1. for (const key in source) {
  2. if (source.hasOwnProperty(key)) {
  3. dist[key] = deepClone(source[key])
  4. }
  5. }
  1. it('自动跳过原型属性', () => {
  2. const a = Object.create({
  3. name: 'a'
  4. })
  5. a.xxx = {
  6. yyy: {
  7. zzz: '1'
  8. }
  9. }
  10. const a2 = deepClone(a)
  11. assert.isFalse('name' in a2)
  12. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  13. assert(a.xxx.yyy !== a2.xxx.yyy)
  14. assert(a.xxx !== a2.xxx)
  15. assert(a !== a2)
  16. })

第一版代码

  1. let cache = []
  2. function deepClone(source) {
  3. if (source instanceof Object) {
  4. let cacheDist = findCache(source)
  5. if (cacheDist) {
  6. return cacheDist
  7. } else {
  8. let dist
  9. if (source instanceof Array) {
  10. dist = new Array()
  11. } else if (source instanceof Function) {
  12. dist = function () {
  13. return source.apply(this, arguments)
  14. }
  15. } else if (source instanceof RegExp) {
  16. dist = new RegExp(source.source, source.flags)
  17. } else if (source instanceof Date) {
  18. dist = new Date(source)
  19. } else {
  20. dist = new Object()
  21. }
  22. cache.push([source, dist])
  23. for (const key in source) {
  24. if (source.hasOwnProperty(key)) {
  25. dist[key] = deepClone(source[key])
  26. }
  27. }
  28. return dist
  29. }
  30. }
  31. return source
  32. }
  33. function findCache(source) {
  34. for (let i = 0; i < cache.length; i++) {
  35. if (cache[i][0] === source) {
  36. return cache[i][1]
  37. }
  38. }
  39. return undefined
  40. }
  41. module.exports = deepClone

测试代码

  1. const chai = require("chai");
  2. const sinon = require("sinon");
  3. const sinonChai = require("sinon-chai");
  4. chai.use(sinonChai);
  5. const assert = chai.assert;
  6. const deepClone = require('../src/index.js')
  7. describe('deepClone', () => {
  8. it("是一个函数", () => {
  9. assert.isFunction(deepClone)
  10. })
  11. it('能够复制基本类型', () => {
  12. const n = 123
  13. const n2 = deepClone(n)
  14. assert(n === n2)
  15. const s = '123456'
  16. const s2 = deepClone(s)
  17. assert(s === s2)
  18. const b = true
  19. const b2 = deepClone(b)
  20. assert(b === b2)
  21. const u = undefined
  22. const u2 = deepClone(u)
  23. assert(u === u2)
  24. const empty = null
  25. const empty2 = deepClone(empty)
  26. assert(empty === empty2)
  27. const symbol = Symbol()
  28. const symbol2 = deepClone(symbol)
  29. assert(symbol === symbol2)
  30. })
  31. describe('复制对象', () => {
  32. it('能够复制普通对象', () => {
  33. const a = {
  34. name: "Gouson",
  35. age: 18,
  36. child: {
  37. name: 'Joe'
  38. }
  39. }
  40. const a2 = deepClone(a)
  41. assert(a !== a2)
  42. assert(a.name === a2.name)
  43. assert(a.child !== a2.child)
  44. assert(a.child.name === a2.child.name)
  45. })
  46. it('能够复制数组对象', () => {
  47. const a = [
  48. [11, 12],
  49. [21, 22],
  50. [31, 32]
  51. ]
  52. const a2 = deepClone(a)
  53. assert(a !== a2)
  54. assert(a[0] !== a2[0])
  55. assert(a[1] !== a2[1])
  56. assert(a[2] !== a2[2])
  57. assert.deepEqual(a, a2)
  58. });
  59. it('能够复制函数', () => {
  60. const a = function () {
  61. return 1
  62. }
  63. a.xxx = {
  64. yyy: {
  65. zzz: 1
  66. }
  67. }
  68. const a2 = deepClone(a)
  69. assert(a !== a2)
  70. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  71. assert(a.xxx.yyy !== a2.xxx.yyy)
  72. assert(a.xxx !== a2.xxx)
  73. assert(a() === a2())
  74. });
  75. it('环也能复制', () => {
  76. const a = {
  77. name: "Gouson"
  78. }
  79. a.self = a
  80. const a2 = deepClone(a)
  81. assert(a !== a2)
  82. assert(a.name === a2.name)
  83. assert(a.self !== a2.self)
  84. })
  85. it('可以复制正则', () => {
  86. const a = new RegExp('hi\\d+', 'gi')
  87. a.xxx = {
  88. yyy: {
  89. zzz: '1'
  90. }
  91. }
  92. const a2 = deepClone(a)
  93. assert(a.source === a2.source)
  94. assert(a.flags === a2.flags)
  95. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  96. assert(a.xxx.yyy !== a2.xxx.yyy)
  97. assert(a.xxx !== a2.xxx)
  98. assert(a !== a2)
  99. })
  100. it('可以复制日期', () => {
  101. const a = new Date()
  102. a.xxx = {
  103. yyy: {
  104. zzz: '1'
  105. }
  106. }
  107. const a2 = deepClone(a)
  108. assert(a.getTime() === a2.getTime())
  109. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  110. assert(a.xxx.yyy !== a2.xxx.yyy)
  111. assert(a.xxx !== a2.xxx)
  112. assert(a !== a2)
  113. })
  114. it('自动跳过原型属性', () => {
  115. const a = Object.create({
  116. name: 'a'
  117. })
  118. a.xxx = {
  119. yyy: {
  120. zzz: '1'
  121. }
  122. }
  123. const a2 = deepClone(a)
  124. assert.isFalse('name' in a2)
  125. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  126. assert(a.xxx.yyy !== a2.xxx.yyy)
  127. assert(a.xxx !== a2.xxx)
  128. assert(a !== a2)
  129. })
  130. });
  131. });

改造成类

现在每次克隆一个对象以后,cache对象没有清空

  1. class DeepCloner {
  2. constructor() {
  3. this.cache = []
  4. }
  5. clone(source) {
  6. if (source instanceof Object) {
  7. let cacheDist = this.findCache(source)
  8. if (cacheDist) {
  9. return cacheDist
  10. } else {
  11. let dist
  12. if (source instanceof Array) {
  13. dist = new Array()
  14. } else if (source instanceof Function) {
  15. dist = function () {
  16. return source.apply(this, arguments)
  17. }
  18. } else if (source instanceof RegExp) {
  19. dist = new RegExp(source.source, source.flags)
  20. } else if (source instanceof Date) {
  21. dist = new Date(source)
  22. } else {
  23. dist = new Object()
  24. }
  25. this.cache.push([source, dist])
  26. for (const key in source) {
  27. if (source.hasOwnProperty(key)) {
  28. dist[key] = this.clone(source[key])
  29. }
  30. }
  31. return dist
  32. }
  33. }
  34. return source
  35. }
  36. findCache(source) {
  37. for (let i = 0; i < this.cache.length; i++) {
  38. if (this.cache[i][0] === source) {
  39. return this.cache[i][1]
  40. }
  41. }
  42. return undefined
  43. }
  44. }
  45. module.exports = DeepCloner
  1. const chai = require("chai");
  2. const sinon = require("sinon");
  3. const sinonChai = require("sinon-chai");
  4. chai.use(sinonChai);
  5. const assert = chai.assert;
  6. const DeepCloner = require('../src/index.js')
  7. describe('clone', () => {
  8. it("是一个类", () => {
  9. assert.isFunction(DeepCloner)
  10. })
  11. it('能够复制基本类型', () => {
  12. const n = 123
  13. const n2 = new DeepCloner().clone(n)
  14. assert(n === n2)
  15. const s = '123456'
  16. const s2 = new DeepCloner().clone(s)
  17. assert(s === s2)
  18. const b = true
  19. const b2 = new DeepCloner().clone(b)
  20. assert(b === b2)
  21. const u = undefined
  22. const u2 = new DeepCloner().clone(u)
  23. assert(u === u2)
  24. const empty = null
  25. const empty2 = new DeepCloner().clone(empty)
  26. assert(empty === empty2)
  27. const symbol = Symbol()
  28. const symbol2 = new DeepCloner().clone(symbol)
  29. assert(symbol === symbol2)
  30. })
  31. describe('复制对象', () => {
  32. it('能够复制普通对象', () => {
  33. const a = {
  34. name: "Gouson",
  35. age: 18,
  36. child: {
  37. name: 'Joe'
  38. }
  39. }
  40. const a2 = new DeepCloner().clone(a)
  41. assert(a !== a2)
  42. assert(a.name === a2.name)
  43. assert(a.child !== a2.child)
  44. assert(a.child.name === a2.child.name)
  45. })
  46. it('能够复制数组对象', () => {
  47. const a = [
  48. [11, 12],
  49. [21, 22],
  50. [31, 32]
  51. ]
  52. const a2 = new DeepCloner().clone(a)
  53. assert(a !== a2)
  54. assert(a[0] !== a2[0])
  55. assert(a[1] !== a2[1])
  56. assert(a[2] !== a2[2])
  57. assert.deepEqual(a, a2)
  58. });
  59. it('能够复制函数', () => {
  60. const a = function () {
  61. return 1
  62. }
  63. a.xxx = {
  64. yyy: {
  65. zzz: 1
  66. }
  67. }
  68. const a2 = new DeepCloner().clone(a)
  69. assert(a !== a2)
  70. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  71. assert(a.xxx.yyy !== a2.xxx.yyy)
  72. assert(a.xxx !== a2.xxx)
  73. assert(a() === a2())
  74. });
  75. it('环也能复制', () => {
  76. const a = {
  77. name: "Gouson"
  78. }
  79. a.self = a
  80. const a2 = new DeepCloner().clone(a)
  81. assert(a !== a2)
  82. assert(a.name === a2.name)
  83. assert(a.self !== a2.self)
  84. })
  85. it('可以复制正则', () => {
  86. const a = new RegExp('hi\\d+', 'gi')
  87. a.xxx = {
  88. yyy: {
  89. zzz: '1'
  90. }
  91. }
  92. const a2 = new DeepCloner().clone(a)
  93. assert(a.source === a2.source)
  94. assert(a.flags === a2.flags)
  95. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  96. assert(a.xxx.yyy !== a2.xxx.yyy)
  97. assert(a.xxx !== a2.xxx)
  98. assert(a !== a2)
  99. })
  100. it('可以复制日期', () => {
  101. const a = new Date()
  102. a.xxx = {
  103. yyy: {
  104. zzz: '1'
  105. }
  106. }
  107. const a2 = new DeepCloner().clone(a)
  108. assert(a.getTime() === a2.getTime())
  109. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  110. assert(a.xxx.yyy !== a2.xxx.yyy)
  111. assert(a.xxx !== a2.xxx)
  112. assert(a !== a2)
  113. })
  114. it('自动跳过原型属性', () => {
  115. const a = Object.create({
  116. name: 'a'
  117. })
  118. a.xxx = {
  119. yyy: {
  120. zzz: '1'
  121. }
  122. }
  123. const a2 = new DeepCloner().clone(a)
  124. assert.isFalse('name' in a2)
  125. assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)
  126. assert(a.xxx.yyy !== a2.xxx.yyy)
  127. assert(a.xxx !== a2.xxx)
  128. assert(a !== a2)
  129. })
  130. it("很复杂的对象", () => {
  131. const a = {
  132. n: NaN,
  133. n2: Infinity,
  134. s: "",
  135. bool: false,
  136. null: null,
  137. u: undefined,
  138. sym: Symbol(),
  139. o: {
  140. n: NaN,
  141. n2: Infinity,
  142. s: "",
  143. bool: false,
  144. null: null,
  145. u: undefined,
  146. sym: Symbol()
  147. },
  148. array: [{
  149. n: NaN,
  150. n2: Infinity,
  151. s: "",
  152. bool: false,
  153. null: null,
  154. u: undefined,
  155. sym: Symbol()
  156. }]
  157. };
  158. const a2 = new DeepCloner().clone(a)
  159. assert(a !== a2);
  160. assert.isNaN(a2.n);
  161. assert(a.n2 === a2.n2);
  162. assert(a.s === a2.s);
  163. assert(a.bool === a2.bool);
  164. assert(a.null === a2.null);
  165. assert(a.u === a2.u);
  166. assert(a.sym === a2.sym);
  167. assert(a.o !== a2.o);
  168. assert.isNaN(a2.o.n);
  169. assert(a.o.n2 === a2.o.n2);
  170. assert(a.o.s === a2.o.s);
  171. assert(a.o.bool === a2.o.bool);
  172. assert(a.o.null === a2.o.null);
  173. assert(a.o.u === a2.o.u);
  174. assert(a.o.sym === a2.o.sym);
  175. assert(a.array !== a2.array);
  176. assert(a.array[0] !== a2.array[0]);
  177. assert.isNaN(a2.array[0].n);
  178. assert(a.array[0].n2 === a2.array[0].n2);
  179. assert(a.array[0].s === a2.array[0].s);
  180. assert(a.array[0].bool === a2.array[0].bool);
  181. assert(a.array[0].null === a2.array[0].null);
  182. assert(a.array[0].u === a2.array[0].u);
  183. assert(a.array[0].sym === a2.array[0].sym);
  184. });
  185. });
  186. });