什么是深拷贝?
- 简单理解
- b是a的一份拷贝,b中没有对a中对象的引用
另一种理解
不支持undefined
- 不支持function
- 不支持Date
- 不支持正则
- 不支持symbol
- 不支持引用,循环引用
递归克隆
思路
- 递归
- 看节点类型(7种)
- 如果是基本类型就直接拷贝
- 如果是object就分情况讨论
object分为
$ yarn init -y$ yarn add -D chai mocha sinon sinon-chai- package.json
"scripts": {"test": "mocha test/**/*.js"}
/src/index.js
function deepClone() {}module.exports = deepClone
/test/index.js
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.only("是一个函数", () => {assert.isFunction(deepClone)})});
代码
可以复制普通对象
function deepClone(source) {if (source instanceof Object) {const dist = new Object()for (const key in source) {dist[key] = deepClone(source[key])}return dist}return source}module.exports = deepClone
可以复制数组
function deepClone(source) {if (source instanceof Object) {if (source instanceof Array) {const dist = new Array()for (const key in source) {dist[key] = deepClone(source[key])}return dist} else {const dist = new Object()for (const key in source) {dist[key] = deepClone(source[key])}return dist}}return source}module.exports = deepClone
可以复制函数
function deepClone(source) {if (source instanceof Object) {if (source instanceof Array) {const dist = new Array()for (const key in source) {dist[key] = deepClone(source[key])}return dist} else if (source instanceof Function) {const dist = function () {return source.apply(this, arguments)}for (const key in source) {dist[key] = deepClone(source[key])}return dist} else {const dist = new Object()for (const key in source) {dist[key] = deepClone(source[key])}return dist}}return source}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(“是一个函数”, () => {
assert.isFunction(deepClone)
}) it(‘能够复制基本类型’, () => {
const n = 123const n2 = deepClone(n)assert(n === n2)const s = '123456'const s2 = deepClone(s)assert(s === s2)const b = trueconst b2 = deepClone(b)assert(b === b2)const u = undefinedconst u2 = deepClone(u)assert(u === u2)const empty = nullconst empty2 = deepClone(empty)assert(empty === empty2)const symbol = Symbol()const symbol2 = deepClone(symbol)assert(symbol === symbol2)
}) describe(‘复制对象’, () => {
it('能够复制普通对象', () => {const a = {name: "Gouson",age: 18,child: {name: 'Joe'}}const a2 = deepClone(a)assert(a !== a2)assert(a.name === a2.name)assert(a.child !== a2.child)assert(a.child.name === a2.child.name)})it('能够复制数组对象', () => {const a = [[11, 12],[21, 22],[31, 32]]const a2 = deepClone(a)assert(a !== a2)assert(a[0] !== a2[0])assert(a[1] !== a2[1])assert(a[2] !== a2[2])assert.deepEqual(a, a2)});it('能够复制函数', () => {const a = function () {return 1}a.xxx = {yyy: {zzz: 1}}const a2 = deepClone(a)assert(a !== a2)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a() === a2())});
});
});
<a name="lWqhW"></a># 解决环引用```javascriptlet cache = []function deepClone(source) {if (source instanceof Object) {let cacheDist = findCache(source)if (cacheDist) {return cacheDist} else {let distif (source instanceof Array) {dist = new Array()} else if (source instanceof Function) {dist = function () {return source.apply(this, arguments)}} else {dist = new Object()}cache.push([source, dist])for (const key in source) {dist[key] = deepClone(source[key])}return dist}}return source}function findCache(source) {for (let i = 0; i < cache.length; i++) {if (cache[i][0] === source) {return cache[i][1]}}return undefined}module.exports = deepClone
it('环也能复制', () => {const a = {name: "Gouson"}a.self = aconst a2 = deepClone(a)assert(a !== a2)assert(a.name === a2.name)assert(a.self !== a2.self)})
考虑爆栈
拷贝RegExp
...else if (source instanceof RegExp) {dist = new RegExp(source.source, source.flags)}...
it('可以复制正则', () => {const a = new RegExp('hi\\d+', 'gi')a.xxx = {yyy: {zzz: '1'}}const a2 = deepClone(a)assert(a.source === a2.source)assert(a.flags === a2.flags)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})
考虑日期
...else if (source instanceof Date) {dist = new Date(source)}...
it('可以复制日期', () => {const a = new Date()a.xxx = {yyy: {zzz: '1'}}const a2 = deepClone(a)assert(a.getTime() === a2.getTime())assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})
原型链
原型链上的属性一般不会进行深拷贝
for (const key in source) {if (source.hasOwnProperty(key)) {dist[key] = deepClone(source[key])}}
it('自动跳过原型属性', () => {const a = Object.create({name: 'a'})a.xxx = {yyy: {zzz: '1'}}const a2 = deepClone(a)assert.isFalse('name' in a2)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})
第一版代码
let cache = []function deepClone(source) {if (source instanceof Object) {let cacheDist = findCache(source)if (cacheDist) {return cacheDist} else {let distif (source instanceof Array) {dist = new Array()} else if (source instanceof Function) {dist = function () {return source.apply(this, arguments)}} else if (source instanceof RegExp) {dist = new RegExp(source.source, source.flags)} else if (source instanceof Date) {dist = new Date(source)} else {dist = new Object()}cache.push([source, dist])for (const key in source) {if (source.hasOwnProperty(key)) {dist[key] = deepClone(source[key])}}return dist}}return source}function findCache(source) {for (let i = 0; i < cache.length; i++) {if (cache[i][0] === source) {return cache[i][1]}}return undefined}module.exports = deepClone
测试代码
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("是一个函数", () => {assert.isFunction(deepClone)})it('能够复制基本类型', () => {const n = 123const n2 = deepClone(n)assert(n === n2)const s = '123456'const s2 = deepClone(s)assert(s === s2)const b = trueconst b2 = deepClone(b)assert(b === b2)const u = undefinedconst u2 = deepClone(u)assert(u === u2)const empty = nullconst empty2 = deepClone(empty)assert(empty === empty2)const symbol = Symbol()const symbol2 = deepClone(symbol)assert(symbol === symbol2)})describe('复制对象', () => {it('能够复制普通对象', () => {const a = {name: "Gouson",age: 18,child: {name: 'Joe'}}const a2 = deepClone(a)assert(a !== a2)assert(a.name === a2.name)assert(a.child !== a2.child)assert(a.child.name === a2.child.name)})it('能够复制数组对象', () => {const a = [[11, 12],[21, 22],[31, 32]]const a2 = deepClone(a)assert(a !== a2)assert(a[0] !== a2[0])assert(a[1] !== a2[1])assert(a[2] !== a2[2])assert.deepEqual(a, a2)});it('能够复制函数', () => {const a = function () {return 1}a.xxx = {yyy: {zzz: 1}}const a2 = deepClone(a)assert(a !== a2)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a() === a2())});it('环也能复制', () => {const a = {name: "Gouson"}a.self = aconst a2 = deepClone(a)assert(a !== a2)assert(a.name === a2.name)assert(a.self !== a2.self)})it('可以复制正则', () => {const a = new RegExp('hi\\d+', 'gi')a.xxx = {yyy: {zzz: '1'}}const a2 = deepClone(a)assert(a.source === a2.source)assert(a.flags === a2.flags)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})it('可以复制日期', () => {const a = new Date()a.xxx = {yyy: {zzz: '1'}}const a2 = deepClone(a)assert(a.getTime() === a2.getTime())assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})it('自动跳过原型属性', () => {const a = Object.create({name: 'a'})a.xxx = {yyy: {zzz: '1'}}const a2 = deepClone(a)assert.isFalse('name' in a2)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})});});
改造成类
现在每次克隆一个对象以后,cache对象没有清空
class DeepCloner {constructor() {this.cache = []}clone(source) {if (source instanceof Object) {let cacheDist = this.findCache(source)if (cacheDist) {return cacheDist} else {let distif (source instanceof Array) {dist = new Array()} else if (source instanceof Function) {dist = function () {return source.apply(this, arguments)}} else if (source instanceof RegExp) {dist = new RegExp(source.source, source.flags)} else if (source instanceof Date) {dist = new Date(source)} else {dist = new Object()}this.cache.push([source, dist])for (const key in source) {if (source.hasOwnProperty(key)) {dist[key] = this.clone(source[key])}}return dist}}return source}findCache(source) {for (let i = 0; i < this.cache.length; i++) {if (this.cache[i][0] === source) {return this.cache[i][1]}}return undefined}}module.exports = DeepCloner
const chai = require("chai");const sinon = require("sinon");const sinonChai = require("sinon-chai");chai.use(sinonChai);const assert = chai.assert;const DeepCloner = require('../src/index.js')describe('clone', () => {it("是一个类", () => {assert.isFunction(DeepCloner)})it('能够复制基本类型', () => {const n = 123const n2 = new DeepCloner().clone(n)assert(n === n2)const s = '123456'const s2 = new DeepCloner().clone(s)assert(s === s2)const b = trueconst b2 = new DeepCloner().clone(b)assert(b === b2)const u = undefinedconst u2 = new DeepCloner().clone(u)assert(u === u2)const empty = nullconst empty2 = new DeepCloner().clone(empty)assert(empty === empty2)const symbol = Symbol()const symbol2 = new DeepCloner().clone(symbol)assert(symbol === symbol2)})describe('复制对象', () => {it('能够复制普通对象', () => {const a = {name: "Gouson",age: 18,child: {name: 'Joe'}}const a2 = new DeepCloner().clone(a)assert(a !== a2)assert(a.name === a2.name)assert(a.child !== a2.child)assert(a.child.name === a2.child.name)})it('能够复制数组对象', () => {const a = [[11, 12],[21, 22],[31, 32]]const a2 = new DeepCloner().clone(a)assert(a !== a2)assert(a[0] !== a2[0])assert(a[1] !== a2[1])assert(a[2] !== a2[2])assert.deepEqual(a, a2)});it('能够复制函数', () => {const a = function () {return 1}a.xxx = {yyy: {zzz: 1}}const a2 = new DeepCloner().clone(a)assert(a !== a2)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a() === a2())});it('环也能复制', () => {const a = {name: "Gouson"}a.self = aconst a2 = new DeepCloner().clone(a)assert(a !== a2)assert(a.name === a2.name)assert(a.self !== a2.self)})it('可以复制正则', () => {const a = new RegExp('hi\\d+', 'gi')a.xxx = {yyy: {zzz: '1'}}const a2 = new DeepCloner().clone(a)assert(a.source === a2.source)assert(a.flags === a2.flags)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})it('可以复制日期', () => {const a = new Date()a.xxx = {yyy: {zzz: '1'}}const a2 = new DeepCloner().clone(a)assert(a.getTime() === a2.getTime())assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})it('自动跳过原型属性', () => {const a = Object.create({name: 'a'})a.xxx = {yyy: {zzz: '1'}}const a2 = new DeepCloner().clone(a)assert.isFalse('name' in a2)assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz)assert(a.xxx.yyy !== a2.xxx.yyy)assert(a.xxx !== a2.xxx)assert(a !== a2)})it("很复杂的对象", () => {const a = {n: NaN,n2: Infinity,s: "",bool: false,null: null,u: undefined,sym: Symbol(),o: {n: NaN,n2: Infinity,s: "",bool: false,null: null,u: undefined,sym: Symbol()},array: [{n: NaN,n2: Infinity,s: "",bool: false,null: null,u: undefined,sym: Symbol()}]};const a2 = new DeepCloner().clone(a)assert(a !== a2);assert.isNaN(a2.n);assert(a.n2 === a2.n2);assert(a.s === a2.s);assert(a.bool === a2.bool);assert(a.null === a2.null);assert(a.u === a2.u);assert(a.sym === a2.sym);assert(a.o !== a2.o);assert.isNaN(a2.o.n);assert(a.o.n2 === a2.o.n2);assert(a.o.s === a2.o.s);assert(a.o.bool === a2.o.bool);assert(a.o.null === a2.o.null);assert(a.o.u === a2.o.u);assert(a.o.sym === a2.o.sym);assert(a.array !== a2.array);assert(a.array[0] !== a2.array[0]);assert.isNaN(a2.array[0].n);assert(a.array[0].n2 === a2.array[0].n2);assert(a.array[0].s === a2.array[0].s);assert(a.array[0].bool === a2.array[0].bool);assert(a.array[0].null === a2.array[0].null);assert(a.array[0].u === a2.array[0].u);assert(a.array[0].sym === a2.array[0].sym);});});});
