多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
开始之前,举个**栗子**! ``` let a = "笑死你", let b = a b = "不想让你死" console.log(a) //快说他输出多少?? console.log(b) //快说他输出多少?? ``` 好的 说出答案了,我们不急,知道大家都是前端大佬,咱们慢慢一步一步来哈。 >**函数也是对象** 底层是对象,只不过叫数组类型 ``` let c = ["猫", "狗", "驴"] let d = c d[0] = “ 羊” console.log(d) console.log(c) ``` ``` let a = ["猫"] let b = [...a] b[0] = "喵喵喵" console.log(b) console.log(a) //这就是深拷贝 ``` ``` let a = [ {a: "小明"} ] let b = [...a] console.log(b) b[0].a = "汪汪叫" console.log(b) console.log(a) ``` 可以发现 如果用...方法拷贝过来的是字符串,是可以实现深拷贝的 。但是当拷贝的是对象的话,就不行了 此时此刻,来了,什么是深拷贝,什么是浅拷贝? ## 如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改B时,看A是否会发生变化,如果A也跟着变了,说明这是浅拷贝,拿人手短,如果A没变,那就是深拷贝。 * 从基本类型和引用的数据存储上面区别理解: **a.基本类型--名值存储在栈内存中**,例如let a=1; ![](https://img.kancloud.cn/f6/7c/f67cfe8d7e7eed2c63e34c465f65b5ec_200x100.png) 当你b=a复制时,栈内存会新开辟一个内存,例如这样: ![](https://img.kancloud.cn/7e/80/7e80ae3629542bfadea493d433c6e8cc_200x150.png) 所以当你此时修改a=2,对b并不会造成影响,因为此时的b已自食其力,翅膀硬了,不受a的影响了。当然,let a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。 **b.引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值**,我们以上面浅拷贝的例子画个图: ![](https://img.kancloud.cn/6a/0a/6a0a4c13a1c8c9f69cef504aedeaa2e5_500x150.png) 当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。 ![](https://img.kancloud.cn/68/b1/68b102eb8bf08377c77a281262e53de8_500x150.png) 而当我们**a\[0\]=1**时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。![](https://img.kancloud.cn/dc/70/dc708ac1b7c55ca12139aa7bb6ec7970_500x150.png) 那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,岂不就达到深拷贝的效果了嘛 ![](https://img.kancloud.cn/b0/d7/b0d7dd6fe4a846e3263eec3e703deaab_500x150.png) ## **现在我们就来实现浅拷贝与深拷贝** * 实现浅拷贝的方法 · 1. for···in只循环第一层 ~~~ // 只复制第一层的浅拷贝 function simpleCopy(obj1) { let obj2 = Array.isArray(obj1) ? [] : {}; for (let i in obj1) { obj2[i] = obj1[i]; } return obj2; } let obj1 = { a: 1, b: 2, c: { d: 3 } } let obj2 = simpleCopy(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // ? alert(obj2.a); // ? alert(obj1.c.d); // ? alert(obj2.c.d); // ? ~~~ 提问,以上各输出啥? 。 。 。 。 。 。 。 。 。 1 3 4 4 · 2. Object.assign方法 Object.assign方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 ~~~jsx var obj = { a: 1, b: 2 } var obj1 = Object.assign(obj); obj1.a = 3; console.log(obj.a) // 3 ~~~ · 3. 直接用=赋值 ~~~jsx let a=[0,1,2,3,4], b=a; console.log(a===b); a[0]=1; console.log(a,b); ~~~ * 实现深拷贝的方法 · 1. 采用递归去拷贝所有层级属性 ~~~jsx function deepClone(obj){ let objClone = Array.isArray(obj)?[]:{}; if(obj && typeof obj==="object"){ for(key in obj){ if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制 if(obj[key]&&typeof obj[key] ==="object"){ objClone[key] = deepClone(obj[key]); }else{ //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; } let a=[1,2,3,4], b=deepClone(a); a[0]=2; console.log(a,b); ~~~ ![](https://img.kancloud.cn/77/5b/775b3e541fadd929e66688546835fa58_407x122.png) · 2. 通过JSON对象来实现深拷贝 >JSON.stringify()的作用是将 JavaScript 对象转换为 JSON 字符串,JSON.parse()可以将JSON字符串转为一个对象 ~~~jsx function deepClone2(obj) { var _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone; } ~~~ JSON对象实现深拷贝的一些问题 \* 无法实现对对象中方法的深拷贝,会显示为 undefined · 3. 通过jQuery的extend方法实现深拷贝 ***$*.extend( \[deep \], target, object1 \[, objectN \] )** **deep**表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝 **target**** Object**类型 目标对象,其他对象的成员属性将被附加到该对象上。 **object1  objectN**可选。 Object类型 第一个以及第N个被合并的对象。 ~~~js var array = [1,2,3,4]; var newArray = $.extend(true,[],array); // true为深拷贝,false为浅拷贝 ~~~ · 4. 手动实现深拷贝 ~~~csharp let obj1 = { a: 1, b: 2 } let obj2 = { a: obj1.a, b: obj1.b } obj2.a = 3; alert(obj1.a); alert(obj2.a); ~~~ · 5. 如果对象的value是基本类型的话,也可以用Object.assign来实现深拷贝,但是要把它赋值给一个空对象 ~~~jsx var obj = { a: 1, b: 2 } var obj1 = Object.assign({}, obj); obj1.a = 3; console.log(obj.a);// 1 ~~~ · 6. 用slice实现对数组的深拷贝 **slice()** 方法可从已有的数组中返回选定的元素 [slice]([https://www.w3school.com.cn/js/jsref\_slice\_array.asp](https://www.w3school.com.cn/js/jsref_slice_array.asp)) ~~~jsx // 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝 // 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝 var arr1 = ["1","2","3"]; var arr2 = arr1.slice(0); arr2[1] = "9"; console.log("数组的原始值:" + arr1 ); console.log("数组的新值:" + arr2 ); ~~~ · 7. 用concat实现对数组的深拷贝 concat() 方法用于连接两个或多个数组。 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。 [concat]([https://www.w3school.com.cn/jsref/jsref\_concat\_array.asp](https://www.w3school.com.cn/jsref/jsref_concat_array.asp)) ~~~jsx // 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝 var arr1 = ["1","2","3"]; var arr2 = arr1.concat(); arr2[1] = "9"; console.log("数组的原始值:" + arr1 ); console.log("数组的新值:" + arr2 ); // 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝 var arr1 = [{a:1},{b:2},{c:3}]; var arr2 = arr1.concat(); arr2[0].a = "9"; console.log("数组的原始值:" + arr1[0].a ); // 数组的原始值:9 console.log("数组的新值:" + arr2[0].a ); // 数组的新值:9 ~~~ · 8.直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。 ~~~jsx function deepClone(initalObj, finalObj) { var obj = finalObj || {}; for (var i in initalObj) { var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况 if(prop === obj) { continue; } if (typeof prop === 'object') { obj[i] = (prop.constructor === Array) ? [] : Object.create(prop); } else { obj[i] = prop; } } return obj; } ~~~ · 9.使用扩展运算符实现深拷贝 ~~~jsx // 当value是基本数据类型,比如String,Number,Boolean时,是可以使用拓展运算符进行深拷贝的 // 当value是引用类型的值,比如Object,Array,引用类型进行深拷贝也只是拷贝了引用地址,所以属于浅拷贝 var car = {brand: "BMW", price: "380000", length: "5米"} var car1 = { ...car, price: "500000" } console.log(car1); // { brand: "BMW", price: "500000", length: "5米" } console.log(car); // { brand: "BMW", price: "380000", length: "5米" } ~~~ 说了这么多,了解深拷贝也不仅仅是为了应付面试题,在实际开发中也是非常有用的。例如后台返回了一堆数据,你需要对这堆数据做操作,但多人开发情况下,你是没办法明确这堆数据是否有其它功能也需要使用,直接修改可能会造成隐性问题,深拷贝能帮你更安全安心的去操作数据,根据实际情况来使用深拷贝,大概就是这个意思。 # **面试题:** 一 、js基本类型的分类以及包含哪些? 基础类型:undefined 、 null、number、string、boolean、symbol 引用类型:object对象类型(**Object 、Array 、Function 、Data**) 对于这两种类型有几个关键知识点: 1 基础类型是按照值进行访问的,可以操作保存在变量中的实际的值。对于引用类型,javascript是不允许直接访问值的,不能直接操作对象的内存空间,在操作对象时候,实际操作是引用,而不是实际的引用。 2 基础类型存在于栈中, ![](https://img.kancloud.cn/88/94/88949a55f0ee3fccfd3d967d79b46557_373x168.png) **引用类型的值是同时保存在栈内存和堆内存中的对象**。 ![](https://img.kancloud.cn/ce/04/ce045bfde8661a800a5c4c7b7e2035dd_636x169.png) 二、js的变量的存储方式 栈(stack) 和 堆(heap) ![](https://img.kancloud.cn/0a/f8/0af843f3c9a0da67e699a95d89f943f3_496x334.png) 关于栈和堆有几个关键点需要了解 1 栈(stack)是后进先出,堆(heap)是先进先出。 2 栈(stack)内存是我们手动分配, 堆(heap)内存是自动分配。 三、如何实现浅拷贝? 四、如何实现深拷贝? 五、 [手写深度比较isEqual](https://www.cnblogs.com/xintangchn/p/13197207.html) 思路:深度比较两个对象,就是要深度比较对象的每一个元素。=> 递归 * 递归退出条件: 1. 被比较的是两个值类型变量,直接用“===”判断 2. 被比较的两个变量之一为null,直接判断另一个元素是否也为null * 提前结束递归: 1. 两个变量keys数量不同 2. 传入的两个参数是同一个变量 * 递归工作 深度比较每一个key ``` function isEqual(obj1, obj2){ //其中一个为值类型或null if(!isObject(obj1) || !isObject(obj2)){ return obj1 === obj2; } //判断是否两个参数是同一个变量 if(obj1 === obj2){ return true; } //判断keys数是否相等 const obj1Keys = Object.keys(obj1); const obj2Keys = Object.keys(obj2); if(obj1Keys.length !== obj2Keys.length){ return false; } //深度比较每一个key for(let key in obj1){ if(!isEqual(obj1[key], obj2[key])){ return false; } } return true; } ```