深度复制,浅层复制
1 javascript中对象的深度克隆 2 2014-09-13 08:43:52 收藏 我要投稿 3 又是一个月多月没有更新博客了,这段时间回学校处理下论文的事情,实习的生活也暂时告一段落(在公司上班,才发现学校里面的生活简直如天堂一般,相信很多已经毕业的小伙伴肯定被我说中了,说中了请给本文点个赞,哈哈!)。希望接下来自己的更新进度能加快,马上又是一年校招时,被虐也好、大牛虐别人也罢,总之祝福各位今年要找工作的小伙伴们好运。那么,今天就聊一下一个常见的笔试、面试题,js中对象的深度克隆。翻了下这个题目,在很多地方出现过,已经算一个老的题目了,但是每年的校招中总会考到,其实想想,这个题目考查的知识点还是蛮多的,尤其是对基础知识的考查。好了,闲话不多说,开始正题。 4 5 6 一、js中的对象 7 8 9 10 谈到对象的克隆,必定要说一下对象的概念。 11 12 13 14 js中的数据类型分为两大类:原始类型和对象类型。(1)原始类型包括:数值、字符串、布尔值、null、undefined(后两个是特殊的原始值,这里不做详细的说明,我的上一篇博客有谈到过一些)(2)对象类型包括:对象即是属性的集合,当然这里又两个特殊的对象----函数(js中的一等对象)、数组(键值的有序集合)。 15 16 17 18 好了既然对象分为这两类,这两种类型在复制克隆的时候是有很大区别的。原始类型存储的是对象的实际数据,而对象类型存储的是对象的引用地址(对象的实际内容单独存放,为了减少数据开销通常存放在内存中)。ps:说到这里,大家要知道,对象的原型也是引用对象,它把原型的方法和属性放在内存当中,通过原型链的方式来指向这个内存地址。 19 20 21 22 二、克隆的概念 23 24 25 26 浅度克隆:原始类型为值传递,对象类型仍为引用传递。 27 28 29 30 深度克隆:所有元素或属性均完全复制,与原对象完全脱离,也就是说所有对于新对象的修改都不会反映到原对象中。 31 32 33 34 三、浅克隆的表现 35 36 37 38 1,原始类型 39 40 41 42 看下面一段代码: 43 44 45 46 复制代码 47 48 //数值克隆的表现 49 50 var a="1"; 51 52 var b=a; 53 54 b="2"; 55 56 console.log(a);// "1" 57 58 console.log(b);// "2" 59 60 61 62 //字符串克隆的表现 63 64 var c="1"; 65 66 var d=c; 67 68 d="2"; 69 70 console.log(c);// "1" 71 72 console.log(d);// "2" 73 74 75 76 //字符串克隆的表现 77 78 var x=true; 79 80 var y=x; 81 82 y=false; 83 84 console.log(x);// true 85 86 console.log(y);// false 87 88 复制代码 89 90 从上面的代码大家可以看出,原始类型即使我们采用普通的克隆方式仍能得到正确的结果,原因就是原始类型存储的是对象的实际数据。 91 92 93 94 2.对象类型 95 96 97 98 前面说过,函数式一等对象,当然也是对象类型,但是函数的克隆通过浅克隆即可实现 99 100 101 102 复制代码 103 104 var m=function(){alert(1);}; 105 106 var n=m; 107 108 n=function(){alert(2);}; 109 110 111 112 console.log(m());//1 113 114 console.log(n());//2 115 116 复制代码 117 118 大家能看到,我们直接通过普通赋值的方式,就实现了函数的克隆,并且不会影响之前的对象。原因就是函数的克隆会在内存单独开辟一块空间,互不影响。 119 120 121 122 好了,说了这个特殊的”关系户“以后,我们来说说普通的”选手“。为了方便后续的代码表现,我这里定义一个复杂的对象类型oPerson。下面看一下对象类型的浅复制有什么危害: 123 124 125 126 复制代码 127 128 var oPerson={ 129 130 oName:"rookiebob", 131 132 oAge:"18", 133 134 oAddress:{ 135 136 province:"beijing" 137 138 }, 139 140 ofavorite:[ 141 142 "swimming", 143 144 {reading:"history book"} 145 146 ], 147 148 skill:function(){ 149 150 console.log("bob is coding"); 151 152 } 153 154 }; 155 156 function clone(obj){ 157 158 var result={}; 159 160 for(key in obj){ 161 162 result[key]=obj[key]; 163 164 } 165 166 return result; 167 168 } 169 170 var oNew=clone(oPerson); 171 172 console.log(oPerson.oAddress.province);//beijing 173 174 oNew.oAddress.province="shanghai"; 175 176 console.log(oPerson.oAddress.province);//shanghai 177 178 复制代码 179 180 通过上面的代码,大家能看到,经过对象克隆以后,我修改oNew的地址,发现原对象oPerson也被修改了。这说明对象的克隆不够彻底,那也就是说深度克隆失败! 181 182 183 184 四、深克隆的实现 185 186 187 188 为了保证对象的所有属性都被复制到,我们必须知道如果for循环以后,得到的元素仍是Object或者Array,那么需要再次循环,直到元素是原始类型或者函数为止。为了得到元素的类型,我们定义一个通用函数,用来返回传入对象的类型。 189 190 191 192 复制代码 193 194 //返回传递给他的任意对象的类 195 196 function isClass(o){ 197 198 if(o===null) return "Null"; 199 200 if(o===undefined) return "Undefined"; 201 202 return Object.prototype.toString.call(o).slice(8,-1); 203 204 } 205 206 复制代码 207 208 PS:Object.prototype.toString.call(o)能直接返回对象的类属性,形如"[object class]"的字符串,我们通过截取class,并能知道传入的对象是什么类型。 209 210 211 212 当然这里有两个疑问需要解释下: 213 214 215 216 (1)为什么不直接用toString方法?这是为了防止对象中的toString方法被重写,为了正确的调用toString()版本,必须间接的调用Function.call()方法 217 218 219 220 (2)为什么不使用typeof来直接判断类型?因为对于Array而言,使用typeof(Array)返回的是object,所以不能得到正确的Array,这里对于后续的数组克隆将产生致命的问题。 221 222 223 224 万事俱备,只欠曹操了,下面就正儿八经的开始克隆。 225 226 227 228 复制代码 229 230 //深度克隆 231 232 function deepClone(obj){ 233 234 var result,oClass=isClass(obj); 235 236 //确定result的类型 237 238 if(oClass==="Object"){ 239 240 result={}; 241 242 }else if(oClass==="Array"){ 243 244 result=[]; 245 246 }else{ 247 248 return obj; 249 250 } 251 252 for(key in obj){ 253 254 var copy=obj[key]; 255 256 if(isClass(copy)=="Object"){ 257 258 result[key]=arguments.callee(copy);//递归调用 259 260 }else if(isClass(copy)=="Array"){ 261 262 result[key]=arguments.callee(copy); 263 264 }else{ 265 266 result[key]=obj[key]; 267 268 } 269 270 } 271 272 return result; 273 274 } 275 276 //返回传递给他的任意对象的类 277 278 function isClass(o){ 279 280 if(o===null) return "Null"; 281 282 if(o===undefined) return "Undefined"; 283 284 return Object.prototype.toString.call(o).slice(8,-1); 285 286 } 287 288 var oPerson={ 289 290 oName:"rookiebob", 291 292 oAge:"18", 293 294 oAddress:{ 295 296 province:"beijing" 297 298 }, 299 300 ofavorite:[ 301 302 "swimming", 303 304 {reading:"history book"} 305 306 ], 307 308 skill:function(){ 309 310 console.log("bob is coding"); 311 312 } 313 314 }; 315 316 //深度克隆一个对象 317 318 var oNew=deepClone(oPerson); 319 320 321 322 oNew.ofavorite[1].reading="picture"; 323 324 console.log(oNew.ofavorite[1].reading);//picture 325 326 console.log(oPerson.ofavorite[1].reading);//history book 327 328 329 330 oNew.oAddress.province="shanghai"; 331 332 console.log(oPerson.oAddress.province);//beijing 333 334 console.log(oNew.oAddress.province);//shanghai 335 336 复制代码 337 338 从上面的代码可以看到,深度克隆的对象可以完全脱离原对象,我们对新对象的任何修改都不会反映到原对象中,这样深度克隆就实现了。 339 340 341 342 这里要注意一点的就是:为什么deepClone这个函数中的result一定要判断类型?这里有一种情况,如果你的result直接是{}对象,我明明传进去的是一个数组,结果你复制完了以后,变成了一个对象了。 343 344 345 346 复制代码 347 348 //深度克隆 349 350 function deepClone(obj){ 351 352 var result={},oClass=isClass(obj); 353 354 // if(oClass==="Object"){ 355 356 // result={}; 357 358 // }else if(oClass==="Array"){ 359 360 // result=[]; 361 362 // }else{ 363 364 // return obj; 365 366 // } 367 368 for(key in obj){ 369 370 var copy=obj[key]; 371 372 if(isClass(copy)=="Object"){ 373 374 result[key]=arguments.callee(copy); 375 376 }else if(isClass(copy)=="Array"){ 377 378 result[key]=arguments.callee(copy); 379 380 }else{ 381 382 result[key]=obj[key]; 383 384 } 385 386 } 387 388 return result; 389 390 } 391 392 function isClass(o){ 393 394 if(o===null) return "Null"; 395 396 if(o===undefined) return "Undefined"; 397 398 return Object.prototype.toString.call(o).slice(8,-1); 399 400 } 401 402 //克隆一个数组 403 404 var arr=["a","b","c"]; 405 406 var oNew=deepClone(arr); 407 408 console.log(oNew);//Object {0: "a", 1: "b", 2: "c"}