Node-JS专精07手写深拷贝03

完整代码

src/idnex.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
let cache = [];
function deepClone(source){
if(source instanceof Object){
let cacheDist = findCache(source)
if(cacheDist){
// console.log("有缓存")
return cacheDist;
}else{
// console.log("没缓存")
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();
}
// 数据源 和 副本 都存入缓存 ,注意一定要 在 dist创建成功之后就把它 存入,防止重复的生成
cache.push([source,dist])
for(let 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;

test/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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");

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 sym = Symbol();
const sym2 = deepClone(sym);
assert(sym === sym2);

})

describe("对象",()=>{
it("能够复制普通对象",()=>{
const a = {name: "方方", chlid: {name: "小方方"}};
const a2 = deepClone(a);
assert(a !== a2);
assert(a.name === a2.name);
assert(a.chai !== a2.chlid);
assert(a.chlid.name === a2.chlid.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(x,y){
return x + y;
}
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(1,2) === a2(1,2));

})

it("环也能复制",()=>{
const a = { name: "方方" }
a.self = a;
const a2 = deepClone(a);
assert(a !== a2);
assert(a.name === a2.name);
assert(a.self !== a2.self);

})

xit("不会爆栈",()=>{
const a = {child:null};
let b = a
for(let i=0;i<20000;i++){
b.child = {
child:null
}
b = b.child
}
const a2 = deepClone(a);
assert(a !== a2);
assert(a.child !== a2.child);
})

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 !== a2);
assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz);
assert(a.xxx.yyy !== a2.xxx.yyy);
assert(a.xxx !== a2.xxx);
})

it("可以复制日期",()=>{
const a = new Date();
a.xxx = { yyy: { zzz: 1 } }
const a2 = deepClone(a);
assert(a.source === a2.source);
assert(a !== a2);
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);
})

it("自动跳过原型属性",()=>{
const a = Object.create({name:"a"})
a.xxx = { yyy: { zzz: 1 } }
const a2 = deepClone(a);
assert(a !== a2);
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);
})

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()
}
],
fn: function(){return "fn"},
date: new Date(),
reg: /test/gi,
}

const a2 = deepClone(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);
assert(a.fn !== a2.fn);
assert(a.fn() === a2.fn());
assert(a.date !== a2.date);
assert(a.date.getTime() === a2.date.getTime());
assert(a.reg !== a2.reg);
assert(a.reg.source === a2.reg.source);
assert(a.reg.flags === a2.reg.flags);
})

})
})

代码存在缺陷

  • cache它是一个全局对象,每次调用 deepClone 都会往里面 push东西
  • 所以你下次 deepClone的时候 就会相互影响

面向对象改造我们的代码

src/index_oop.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class DeepCloner{
cache = [];
clone(source){
if(source instanceof Object){
let cacheDist = this.findCache(source)
if(cacheDist){
// console.log("有缓存")
return cacheDist;
}else{
// console.log("没缓存")
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();
}
// 数据源 和 副本 都存入缓存 ,注意一定要 在 dist创建成功之后就把它 存入,防止重复的生成
this.cache.push([source,dist])
for(let 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;

test/index_oop.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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_oop");

describe('DeepCloner',()=>{
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 sym = Symbol();
const sym2 = new DeepCloner().clone(sym);
assert(sym === sym2);

})

describe("对象",()=>{
it("能够复制普通对象",()=>{
const a = {name: "方方", chlid: {name: "小方方"}};
const a2 = new DeepCloner().clone(a);
assert(a !== a2);
assert(a.name === a2.name);
assert(a.chai !== a2.chlid);
assert(a.chlid.name === a2.chlid.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(x,y){
return x + y;
}
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(1,2) === a2(1,2));

})

it("环也能复制",()=>{
const a = { name: "方方" }
a.self = a;
const a2 = new DeepCloner().clone(a);
assert(a !== a2);
assert(a.name === a2.name);
assert(a.self !== a2.self);

})

xit("不会爆栈",()=>{
const a = {child:null};
let b = a
for(let i=0;i<20000;i++){
b.child = {
child:null
}
b = b.child
}
const a2 = new DeepCloner().clone(a);
assert(a !== a2);
assert(a.child !== a2.child);
})

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 !== a2);
assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz);
assert(a.xxx.yyy !== a2.xxx.yyy);
assert(a.xxx !== a2.xxx);
})

it("可以复制日期",()=>{
const a = new Date();
a.xxx = { yyy: { zzz: 1 } }
const a2 = new DeepCloner().clone(a);
assert(a.source === a2.source);
assert(a !== a2);
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);
})

it("自动跳过原型属性",()=>{
const a = Object.create({name:"a"})
a.xxx = { yyy: { zzz: 1 } }
const a2 = new DeepCloner().clone(a);
assert(a !== a2);
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);
})

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()
}
],
fn: function(){return "fn"},
date: new Date(),
reg: /test/gi,
}

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);
assert(a.fn !== a2.fn);
assert(a.fn() === a2.fn());
assert(a.date !== a2.date);
assert(a.date.getTime() === a2.date.getTime());
assert(a.reg !== a2.reg);
assert(a.reg.source === a2.reg.source);
assert(a.reg.flags === a2.reg.flags);
})

})

})

这样一个我们就解决了 cache被全局共享造成影响的问题

面试小技巧

  • cache是我们预留的坑,你打王者肯定不会上来先放大招,那样打不死,肯定是小技能磨血,然后一套技能接大招带走
  • 这样面试官就会惊讶于怎么我一说你就能很快的作出反应。就很加分。

常见回答

  • Object.assign({},a) 这是一个浅拷贝
  • Lodash.cloneDeep
  • 扩展操作符{...a} 它是无法深度克隆的

回到答题思路

五步

  • why 要解决什么问题
  • how 如何解决的
  • pros 优点:只要是你自己实现的基本没什么优点,唯一能证明就是 js基础掌握的较好,因为一般都是用第三方的库,因为第三方的库测试比你完善
  • cons 缺点
    • 如果你的类型超过了我的 if else 我就搞不定了,比如 set / map
  • more 解决缺点的方案
    • 直接用第三方库 比如 lodash / immutable.js

面试亮点

  • 除了写代码,你可以在上面任何一点出彩

代码仓库