思路
递归
- 看节点的类型 7种(
number bool string undefined null symbol 和 object
)array function regexp date 是 object的子类型
- 如果是简单数据类型就直接拷贝
- 如果是 object 就情况讨论
Object
- 普通 object 的
for in
?for in 有bug
- 数组 array - Array初始化
- 它里面可能还是对象
- 函数 function - 怎么拷贝? 闭包?
- 日期 Date - 怎么拷贝?
开始干
创建目录
1
2
3
4
5根目录
deep-clone
src/index.js
test/index.js引入 chai 和 sinon
- 参考代码引入库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17npm init -y
加入如下内容
"scripts": {
"test": "mocha test/**/*.js"
},
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^6.2.0",
"sinon": "^7.4.1",
"sinon-chai": "^3.3.0"
}
yarn install
运行 yarn test
如果显示 0 passing (1ms) 代表环境搭建成功
- 参考代码引入库
开始驱动测试开发
src/index.js
1
2function deepClone(){}
module.exports = deepClone;注意! 由于是js
- 引入模块:js是不支持 import的 所以只能用 require
- 导出模块:js是不支持
export default
所以只能用module.exports = xxx
- test/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 注意!!!
// js是不支持 import的 所以只能用 require
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)
})
})
测试失败 =》 改代码 =》 测试成功 =》 加测试 =》 测试失败
- 这就是永动机
鸡贼的完成基本数据类型深拷贝
src/index.js
1 | function deepClone(source){ |
test/index.js
1 | const chai = require("chai"); |
Symbol的疑问? 为什么会相等
- 不是说不会有两个相同的 Symbol吗?它到底是引用呢?还是没有引用啊?
1 | var a = Symbol() |
复制简单对象
test/index.js
1 | describe("对象",()=>{ |
通常你会这样想
for in的坑
你怎么知道 key是 source的属性 还是它原型上的属性
1 | function deepClone(source){ |
试试数组
1 | it("能够复制数组对象",()=>{ |
修改src/index.js
- 注意此时不要优化代码
- 注意此时不要优化代码
- 注意此时不要优化代码
1 | function deepClone(source){ |
复制函数
test/index.js
1 | it("能够复制函数",()=>{ |
网上的一个思路 eval
1 | // 浏览器控制台里 |
我们的思路是 call / apply
- 唯一的缺陷就是比原来的函数多嵌套了一层
1 | function deepClone(source){ |
环检测
- 我们完成的递归都是对象有结尾的,而如果对象是个环状的就有问题了
1 | var a = {name:"方方"} |
思路
优化代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function deepClone(source){
if(source instanceof Object){
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();
}
for(let key in source){
dist[key] = deepClone(source[key]);
}
return dist;
}
return source;
}
module.exports = deepClone;你要把出现过的 节点 和 克隆后的节点 都保存起来
src/index.js
1 | let cache = []; |
如果这个对象不是环,就是特别深呢?
- 由于你用的是递归它会使用调用栈,所以在一定层数后它会爆栈
1 | a.x.x.x.x.x.x.x.x.x.x 大概2w层呢? |
test/index.js
1 | xit("不会爆栈",()=>{ |
如何解决递归爆栈问题?
- 就是把递归变成循环
- 每进入一个对象,不要去复制他,先把它放到数组里,然后把它的子元素放在数组后面
- 就是把竖着的拍平
- 本次不实现这个功能!!!
- 面试一般不会考到这一步,因为要把上面所有的思路改掉
如果面试问到
- 可能会遇到爆栈
- 解决办法是对它的结构进行一个改造,用循环的方式把它放在数组里
复制正则表达式和Date
- 首先要去看文档,起码要知道 正则有那些属性
测试用例
1 | it("可以复制正则表达式",()=>{ |
src/index.js
1 | let cache = []; |
是否拷贝原型属性?
1 | var a = {name:'hi'} |
- 一般来说不拷贝,因为如果拷贝这个对象内存占用就太大了
- 因为一旦你拷贝这一层,那它的原型链的所有原型都要拷贝,这个原型上的每个函数也有原型那岂不是都要拷贝
- 所以你开了一个头,那整个 JS对象身上的东西都要拷贝,这样占用的内存就太大了
测试代码
1 | it("自动跳过原型属性",()=>{ |
尽量少用for in 复制属性
代码仓库