什么是深拷贝?
- 简单理解
- 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 = 123
const n2 = deepClone(n)
assert(n === n2)
const s = '123456'
const s2 = deepClone(s)
assert(s === s2)
const b = true
const b2 = deepClone(b)
assert(b === b2)
const u = undefined
const u2 = deepClone(u)
assert(u === u2)
const empty = null
const 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>
# 解决环引用
```javascript
let cache = []
function deepClone(source) {
if (source instanceof Object) {
let cacheDist = findCache(source)
if (cacheDist) {
return cacheDist
} else {
let dist
if (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 = a
const 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 dist
if (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 = 123
const n2 = deepClone(n)
assert(n === n2)
const s = '123456'
const s2 = deepClone(s)
assert(s === s2)
const b = true
const b2 = deepClone(b)
assert(b === b2)
const u = undefined
const u2 = deepClone(u)
assert(u === u2)
const empty = null
const 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 = a
const 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 dist
if (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 = 123
const n2 = new DeepCloner().clone(n)
assert(n === n2)
const s = '123456'
const s2 = new DeepCloner().clone(s)
assert(s === s2)
const b = true
const b2 = new DeepCloner().clone(b)
assert(b === b2)
const u = undefined
const u2 = new DeepCloner().clone(u)
assert(u === u2)
const empty = null
const 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 = a
const 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);
});
});
});