建设用地预审系统官方网站,北京装饰公司排名,微信聚合聊天crm系统,嘉峪关市建设局公示公告网站「引言」❝臣闻求木之长者#xff0c;必固其根本#xff1b;欲流之远者#xff0c;必浚其泉源。---- 魏征 《谏太宗十思疏》❞或许你会问到#xff0c;网上已经把深浅拷贝(算一个面试的高频考点了吧)的文章都快写烂了#xff0c;为什么自己还要重新操刀写一遍呢#xff0… 「引言」❝臣闻求木之长者必固其根本欲流之远者必浚其泉源。 ---- 魏征 《谏太宗十思疏》❞或许你会问到网上已经把深浅拷贝(算一个面试的高频考点了吧)的文章都快写烂了为什么自己还要重新操刀写一遍呢❝首先一些文章讲不清也道不明本质另外确实有很优秀的人写的很是生动让我直接看到了风景却不知道沿途是不是也有自己错过的美景唯有尝试过才会真正成为自己的~❞首先我们先来看一张笔者整理的脑图梳理一下~希望通过本文的总结你会有以下几点收获什么是深浅拷贝他们与赋值有何区别浅拷贝的实现方式有哪些深拷贝的实现方式有哪些本章节直接从拷贝开始说起,对于基本数据类型引用数据类型之前的区别,可以看看上面的思维导图引用数据类型拷贝我们从以下三个方面来看看这块的内容赋值浅拷贝深拷贝赋值引用类型的赋值是传址。其引用指向堆中的同一个对象因此操作其中一个对象另一个对象是会跟着一起变的。举个栗子let lucy { name: lucy, age: 23}let lilei lucylilei.name lileililei.age 24console.log(lucy, lucy) // lucy {name: lilei, age: 24}console.log(lilei, lilei) // lilei {name: lilei, age: 24}上面栗子中可以看出来修改了 lilei 的数据lucy也会跟着变。这是初学者(笔者也曾这样)经常犯的一个错后来深刻理解了对象内存的重要性改掉了这个恶习~那么我们该如何不让彼此之间不影响呢接下来我们引出了 拷贝这个概念拷贝又分深拷贝和浅拷贝。来看一看具体是什么和相关区别吧。「注意」对于基本数据类型而言并没有深浅拷贝的区别深浅拷贝都是对于引用数据类型而言的如果我们要赋值对象的所有属性都不是引用类型时我们可以使用浅拷贝遍历并复制最后返回一个对象「本质使用场景」都是复杂对象就是说对象的属性还是对象浅拷贝「本质」只复制一层对象当对象的属性是引用类型时实质复制的是其引用当引用值指向发生改变时也会跟着改变「原理」遍历并复制最后返回一个对象来动手实现一个简单的浅拷贝吧// 实现浅拷贝 for in let shallowCopy (obj) { let rst {} for (let key in obj) { // 只复制本身拥有的属性(非继承过来的属性) if (obj.hasOwnProperty(key)) { rst[key] obj[key] } } return rst}let lucy { name: lucy, age: 23, hobby: [running, swimming]}let lilei shallowCopy(lucy)lilei.name lileililei.age 24lilei.hobby[0] readingconsole.log(lucy, lucy)// lucy {name: lucy, age: 23, hobby: [reading, swimming]}console.log(lilei, lilei)// lilei {name: lilei, age: 24, hobby: [reading, swimming]}我们可以看到当对象的属性是引用类型时实质复制的是其引用当引用值指向发生改变时也会跟着改变。深拷贝「实质」深拷贝出来的对象会互不影响「原理」对对象中子对象进行递归拷贝我们下面会手写一个深拷贝哈~接着往下看会有不一样的收货浅拷贝的实现方式平常用到的浅拷贝有以下几种(欢迎评论补充互相分享进步)Object.assign()扩展运算符(...)Array.prototype.slice()Object.assign()首先 Object.assign(target, source)可以把n个源对象拷贝到目标对象中去(这不是本节重点讨论的内容先一笔带过)然后呢Object.assign 是 ES6新增的对象方法那么它到底是一个深拷贝还是一个浅拷贝的方法呢告诉你一个绝招吧(小点声)「拷贝对象时第一级属性是深拷贝以后级别浅拷贝」举个栗子你就知道了let lucy { name: lucy, age: 23, hobby: [running, swimming]}let lilei Object.assign({}, lucy)lilei.name lileililei.age 24lilei.hobby[0] readingconsole.log(lucy, lucy)// lucy {name: lucy, age: 23, hobby: [reading, swimming]}console.log(lilei, lilei)// lilei {name: lilei, age: 24, hobby: [reading, swimming]}可以看出这个和咱们上面实现的那个浅拷贝的结果是一样的。还是那句话「拷贝对象时第一级属性是深拷贝以后级别浅拷贝」是不是简简单单呢~扩展运算符(...)这个和 Object.assign 一样我们来看个栗子验证一下let lucy { name: lucy, age: 23, hobby: [running, swimming]}let lilei {...lucy}lilei.name lileililei.age 24lilei.hobby[0] readingconsole.log(lucy, lucy)// lucy {name: lucy, age: 23, hobby: [reading, swimming]}console.log(lilei, lilei)// lilei {name: lilei, age: 24, hobby: [reading, swimming]}哦~一毛一样啊和上面。Array.prototype.slice()说到这个方法我第一次看见的时候是在看 vue 源码的时候那个时候真是涨见识(姿势)了话不多说看一下就知道// Dep notify 方法Dep.prototype.notify function notify() { var subs this.subs.slice() // ...}利用了slice() 方法会返回一个新的数组对象但也是一个浅拷贝的方法。即「拷贝对象时第一级属性是深拷贝以后级别浅拷贝」看一个具体的栗子let a1 [1, 2, [3, 4]]let a2 a1.slice()a2[1] 3a2[2][0] 5console.log(a1, a1) // a1 (3) [1, 2, [5, 4]]console.log(a2, a2) // a2 (3) [1, 3, [5, 4]]是不是验证了这个道理呢~同时也要去「注意」 concat这些会返回一个新的数组对象方法等避免造成一些工作开发者不必要的困扰~深拷贝的实现方式深拷贝拷贝出来的对象互不影响但深拷贝相比于浅拷贝速度会比较慢且开销会较大所以考虑清楚数据结构有几层不是很复杂的数据结构建议浅拷贝来节省性能。看一种最简单的深拷贝实现方式JSON.parse(JSON.stringify())**原理**能将json的值json化就是指纯JSON数据不包含循环引用循环引用会报错拿之前的栗子改造一下看看有哪些需要注意的地方let lucy { name: lucy, age: 23, hobby: [running, swimming], say: function() { return this.name }, other: undefined}let lilei JSON.parse(JSON.stringify(lucy))lilei.name lileililei.age 24lilei.hobby[0] readingconsole.log(lucy, lucy)// lucy {// name: lucy,// age: 23,// hobby: [running, swimming],// say: function() {// return this.name// },// other: undefined// }console.log(lilei, lilei)// lilei {age: 24, hobby: [reading, swimming], name: lilei}可以看出来这个方法还是挺强大的。但是也能发现一些问题会忽略 undefined Symbol不能序列化函数不能解决循环引用的对象不能处理正则不能正确处理 new Date() (转换成时间戳可以拷贝)此外深拷贝的其他方法还有 jQuery.extend()以及一些三方库实现的深拷贝 lodash.cloneDeep()等等。大家感兴趣可自行了解继续深造~重头戏面试常考手写一个深拷贝哈哈哈是不是就等这个呢~我们改造一下上面的浅拷贝递归实现深拷贝// 判断边界 null 这个特殊情况let isObject obj typeof obj object obj ! null// 递归实现深拷贝let deepClone (obj) { // 先判断是数组还是对象 let newObj Array.isArray(obj) ? [] : {} if (isObject(obj)) { for (let key in obj) { if (obj.hasOwnProperty(key)) { if (isObject(obj[key])) { // 递归调用每一层 newObj[key] deepClone(obj[key]) } else { newObj[key] obj[key] } } } } return newObj}let aa { name: aa, car: [宝马, 奔驰], driver: function () { }, age: undefined}let bb deepClone(aa) // 全部拷贝了一份bb.name bbbb.age 20bb.driver xxxconsole.log(bb) // { name: bb, car: [ 宝马, 奔驰 ], driver: xxx, age: 20 }console.log(aa)// { name: aa, car: [ 宝马, 奔驰 ], driver: function() {}, age: undefined }可以看出来咱们这个递归实现的深拷贝规避掉了 上面 JSON.parse(JSON.stringify())的一些弊端。但是还存在一些问题循环检测的问题拷贝一个Symbol类型的值又该怎么解决如何解决递归爆栈的问题哈希表针对于循环检测我们可以使用哈希检测的方法比如设置一个数组或者是已经拷贝的对象当检测到对象已经存在哈希表时就去除该值。let isObject obj typeof obj object obj ! null;let deepClone (source, hash new WeakMap()) { if (!isObject(source)) return source // 非对象返回自身 if (hash.has(source)) return hash.get(source) // 新增检测, 查哈希表 let target Array.isArray(source) ? [] : {} hash.set(source, target) // 设置哈希表值 for (let key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] isObject(source[key]) ? deepClone(source[key], hash) : source[key]; // 传入哈希表 } } return target}let obj { a: 1, b: { c: 2, d: 3 }}obj.a obj.b;obj.b.c obj.a;let clone_obj deepClone(obj)console.log(clone_obj)上面实现有点难度如果未能一下看透不妨先跳过完成之前的那个深拷贝就够了当然我喜欢不惧困难的人~剩下的两个就交给喜欢深度思考的人来去头脑风暴一下吧。最后总结一下和原数据是否指向同一个对象第一层数据为基本数据类型原数据中包含子对象赋值是改变会影响原数据改变会影响原数据浅拷贝否改变「不会」影响原数据改变会影响原数据深拷贝是改变「不会」影响原数据改变「不会」影响原数据写在最后❝享受过程带来的喜悦学会去克服自己的缺点❞