周末赋闲在家,由于太冷了,不想出门,索性宅一天好了。可是闲着没事作老是很无聊的,正好新的一年想抓一下童鞋同窗的代码质量,就随便打开了几个童鞋写的代码。因而故事就展开了。javascript
团队大了以后,如何统一团队代码风格实际上是一个蛮重要的问题,目前咱们团队使用lint的方式进行了限制,此次的review能够说是初见成效,除了很多同窗偷偷摸摸的经过noverify的方式提交代码之外。不过没看多久就发现了一段有趣的代码:java
function deepClone(obj, res = {}) { const _res = res; for(let key in obj) { if (obj[key] == obj) { continue; } if (typeof obj[key] === 'object') { if(Array.isArray(obj[key])){ _res[key] = obj[key].slice(); } else { _res[key] = deepClone(obj[key], _res[key]); } } else { _res[key] = obj[key]; } } return _res; }
初看上去就会好奇,为何不使用lodash现成的深拷贝呢?一看是个h5的项目,推测多是为了总体包的大小作了取舍,也无可厚非吧。不过仔细看代码,乍一看好像还挺好,还细心的考虑的数组的状况,可是再仔细看得时候又以为好像有什么地方不对,若是入参是个字符串感情你给别人返回一个空对象么。。跑了一个case发现果真有点问题:node
var c = { a:1 }; var d = new Map(); d.set('a', 1); var a = { a: 1, b: true, c: ()=>{console.log(123)}, d: [1,2], e: d, f: c, g: {} } var b = deepClone(a);
一、虽然正常的处理好像都没有什么问题,可是遇到新的数据结构如Map的时候,这种拷贝就会出问题;数组
二、另外,它只处理了单层循环引用的状况,多层的时候状况会更复杂;安全
三、并且这样递归,层级一深还会有爆栈的隐患,至关的不安全...网络
四、虽然它对单独处理的数组的拷贝,但若是数组的某项的值是一个对象,它这样的处理依然有问题...数据结构
因此深拷贝究竟应该怎么写呢?google
本着能google不手写的原则,查了下网络,好的写法没发现几个,却是几个误区经有的文章常常会提到且一笔略过:对象
一、JSON.parse(JSON.stringify(obj)) 的实现究竟算不算深拷贝?blog
固然算,可是这种实现有几个潜在风险:
1)它的原理是将可以JSON化的值JSON化,再从新生成一个新的JSON对象。因此它可以实现的基础是这个值是可以被JSON化的,像诸如function、map、set全是不能JSON化的,一转就没了。
2) 它还有一个风险是在处理循环引用时是会报错的。这点不少童鞋在实操的时候特别容易忽略,特别是在node端进行端端通讯的时候,曾经一个报错查半天,真的是血的教训。
因此,若是是纯JSON的数据的深拷贝且不包含循环引用,是可使用这个方法的
二、递归在js中是有风险的
常见的实现都是基于递归的,可是递归自己在js的runtime,很容易由于层级过深而致使爆栈。
而一般的方式则是经过“拍平”树级结构的对象成一个数组,来进行拷贝。
三、深拷贝的状况因业务场景的定义会有不一样
有些业务场景须要保持拷贝对象中的值的引用关系不变,而有些却要改变。另外,js的数据结构发展到今天,须要在拷贝时处理的边界状况已经不少了,你须要好好考虑清楚哪些状况须要怎么处理。
其实话说回来,仔细看得话,你会发现第一种写法和jQuery中extend的方式实际上是很像的,一般的状况也基本能覆盖了,不过在如今这个语境下,相比比较“安全”的实现深拷贝,仍是建议使用lodash的cloneDeep(相比jQuery和underscore深拷贝大概60行左右的代码,lodash使用了近几百行代码,考虑了各类边界状况,也可谓是业界楷模了)