首页 文章 精选 留言 我的

精选列表

搜索[前端设计],共10000篇文章
优秀的个人博客,低调大师

前端阿里面试—— 实现一个深拷贝

前言 深拷贝这个功能在开发中经常使用到,特别在对引用类型的数据进行操作时,一般会先深拷贝一份赋值给一个变量,然后在对其操作,防止影响到其它使用该数据的地方。 如何实现一个深拷贝,在面试中出现频率一直居高不下。因为在实现一个深拷贝过程中,可以看出应聘者很多方面的能力。 本专栏将从青铜到王者来介绍怎么实现一个深拷贝,以及每个段位对应的能力。 青铜段位 JSON.parse(JSON.stringify(data)) 复制代码 这种写法非常简单,而且可以应对大部分的应用场景,但是它有很大缺陷的。如果你不知道它有那些缺陷,而且这种实现方法体现不出你任何能力,所以这种实现方法处于青铜段位。 如果对象中存在循环引用的情况也无法正确实现深拷贝。 const a = { b: 1, } a.c = a; JSON.parse(JSON.stringify(a)); 如果data里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象。 const a = { b: new Date(1536627600000), } console.log(JSON.parse(JSON.stringify(a))) 如果data里有RegExp、Error对象,则序列化的结果将只得到空对象; const a = { b: new RegExp(/\d/), c: new Error('错误') } console.log(JSON.parse(JSON.stringify(a))) 如果data里有函数,undefined,则序列化的结果会把函数置为undefined或丢失; const a = { b: function (){ console.log(1) }, c:1, d:undefined } console.log(JSON.parse(JSON.stringify(a))) 如果data里有NaN、Infinity和-Infinity,则序列化的结果会变成null const a = { b: NaN, c: 1.7976931348623157E+10308, d: -1.7976931348623157E+10308, } console.log(JSON.parse(JSON.stringify(a))) 白银段位 深拷贝的核心就是对引用类型的数据的拷贝处理。 function deepClone(target){ if(target !== null && typeof target === 'object'){ let result = {} for (let k in target){ if (target.hasOwnProperty(k)) { result[k] = deepClone(target[k]) } } return result; }else{ return target; } } 以上代码中,deepClone函数的参数target是要深拷贝的数据。 执行target !== null && typeof target === 'object'判断target是不是引用类型。 若不是,直接返回target。 若是,创建一个变量result作为深拷贝的结果,遍历target,执行deepClone(target[k])把target每个属性的值深拷贝后赋值到深拷贝的结果对应的属性result[k]上,遍历完毕后返回result。 在执行deepClone(target[k])中,又会对target[k]进行类型判断,重复上述流程,形成了一个递归调用deepClone函数的过程。就可以层层遍历要拷贝的数据,不管要拷贝的数据有多少子属性,只要子属性的值的类型是引用类型,就会调用deepClone函数将其深拷贝后赋值到深拷贝的结果对应的属性上。 另外使用for...in循环遍历对象的属性时,其原型链上的所有属性都将被访问,如果只要只遍历对象自身的属性,而不遍历继承于原型链上的属性,要使用hasOwnProperty方法过滤一下。 在这里可以向面试官展示你的三个编程能力。 对原始类型和引用类型数据的判断能力。 对递归思维的应用的能力。 深入理解for...in的用法。 黄金段位 白银段位的代码中只考虑到了引用类型的数据是对象的情况,漏了对引用类型的数据是数组的情况。 function deepClone(target){ if(target !== null && typeof target === 'object'){ let result = Object.prototype.toString.call(target) === "[object Array]" ? [] : {}; for (let k in target){ if (target.hasOwnProperty(k)) { result[k] = deepClone(target[k]) } } return result; }else{ return target; } } 以上代码中,只是额外增加对参数target是否是数组的判断。执行Object.prototype.toString.call(target) === "[object Array]"判断target是不是数组,若是数组,变量result为[],若不是数组,变量result为{}。 在这里可以向面试官展示你的两个编程能力。 正确理解引用类型概念的能力。 精确判断数据类型的能力。 铂金段位 假设要深拷贝以下数据data let data = { a: 1 }; data.f=data 执行deepClone(data),会发现控制台报错,错误信息如下所示。 这是因为递归进入死循环导致栈内存溢出了。根本原因是data数据存在循环引用,即对象的属性间接或直接的引用了自身。 function deepClone(target) { function clone(target, map) { if (target !== null && typeof target === 'object') { let result = Object.prototype.toString.call(target) === "[object Array]" ? [] : {}; if (map[target]) { return map[target]; } map[target] = result; for (let k in target) { if (target.hasOwnProperty(k)) { result[k] = deepClone(target[k]) } } return result; } else { return target; } } let map = {} const result = clone(target, map); map = null; return result } 以上代码中利用额外的变量map来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去map中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。最后需要把变量map置为null,释放内存,防止内存泄露。 在这里可以向面试官展示你的两个编程能力。 对循环引用的理解,如何解决循环引用引起的问题的能力。 对内存泄露的认识和避免泄露的能力。 砖石段位 该段位要考虑性能问题了。在上面的代码中,我们遍历数组和对象都使用了for...in这种方式,实际上for...in在遍历时效率是非常低的,故用效率比较高的while来遍历。 function deepClone(target) { /** * 遍历数据处理函数 * @array 要处理的数据 * @callback 回调函数,接收两个参数 value 每一项的值 index 每一项的下标或者key。 */ function handleWhile(array, callback) { const length = array.length; let index = -1; while (++index < length) { callback(array[index], index) } } function clone(target, map) { if (target !== null && typeof target === 'object') { let result = Object.prototype.toString.call(target) === "[object Array]" ? [] : {}; if (map[target]) { return map[target]; } map[target] = result; const keys = Object.prototype.toString.call(target) === "[object Array]" ? undefined : Object.keys( target); function callback(value, key) { if (keys) { // 如果keys存在则说明value是一个对象的key,不存在则说明key就是数组的下标。 key = value; } result[key] = clone(target[key], map) } handleWhile(keys || target, callback) return result; } else { return target; } } let map = {} const result = clone(target, map); map = null; return result } 用while遍历的深拷贝记为deepClone,把用for ... in遍历的深拷贝记为deepClone1。利用console.time()和console.timeEnd()来计算执行时间。 let arr = []; for (let i = 0; i < 1000000; i++) { arr.push(i) } let data = { a: arr }; console.time(); const result = deepClone(data); console.timeEnd(); console.time(); const result1 = deepClone1(data); console.timeEnd(); 从上图明显可以看到用while遍历的深拷贝的性能远优于用for ... in遍历的深拷贝。 在这里可以向面试官展示你的四个编程能力。 具有优化代码运行性能的能力。 了解遍历的效率的能力。 了解++i和i++的区别。 代码抽象的能力。 星耀段位 在这个阶段应该考虑代码逻辑的严谨性。在上面段位的代码虽然已经满足平时开发的需求,但是还是有几处逻辑不严谨的地方。 判断数据不是引用类型时就直接返回target,但是原始类型中还有 Symbol 这一特殊类型的数据,因为其每个 Symbol 都是独一无二,需要额外拷贝处理,不能直接返回。 判断数据是不是引用类型时不严谨,漏了typeof target === function'的判断。 只考虑了 Array、Object 两种引用类型数据的处理,引用类型的数据还有Function 函数、Date 日期、RegExp 正则、Map 数据结构、Set 数据机构,其中 Map 、Set 属于 ES6 的。 废话不多说,直接贴上全部代码,代码中有注释。 function deepClone(target) { // 获取数据类型 function getType(target) { return Object.prototype.toString.call(target) } //判断数据是不是引用类型 function isObject(target) { return target !== null && (typeof target === 'object' || typeof target === 'function'); } //处理不需要遍历的应引用类型数据 function handleOherData(target) { const type = getType(target); switch (type) { case "[object Date]": return new Date(target) case "[object RegExp]": return cloneReg(target) case "[object Function]": return cloneFunction(target) } } //拷贝Symbol类型数据 function cloneSymbol(targe) { const a = String(targe); //把Symbol字符串化 const b = a.substring(7, a.length - 1); //取出Symbol()的参数 return Symbol(b); //用原先的Symbol()的参数创建一个新的Symbol } //拷贝正则类型数据 function cloneReg(target) { const reFlags = /\w*$/; const result = new target.constructor(target.source, reFlags.exec(target)); result.lastIndex = target.lastIndex; return result; } //拷贝函数 function cloneFunction(targe) { //匹配函数体的正则 const bodyReg = /(?<={)(.|\n)+(?=})/m; //匹配函数参数的正则 const paramReg = /(?<=\().+(?=\)\s+{)/; const targeString = targe.toString(); //利用prototype来区分下箭头函数和普通函数,箭头函数是没有prototype的 if (targe.prototype) { //普通函数 const param = paramReg.exec(targeString); const body = bodyReg.exec(targeString); if (body) { if (param) { const paramArr = param[0].split(','); //使用 new Function 重新构造一个新的函数 return new Function(...paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { //箭头函数 //eval和函数字符串来重新生成一个箭头函数 return eval(targeString); } } /** * 遍历数据处理函数 * @array 要处理的数据 * @callback 回调函数,接收两个参数 value 每一项的值 index 每一项的下标或者key。 */ function handleWhile(array, callback) { let index = -1; const length = array.length; while (++index < length) { callback(array[index], index); } } function clone(target, map) { if (isObject(target)) { let result = null; if (getType(target) === "[object Array]") { result = [] } else if (getType(target) === "[object Object]") { result = {} } else if (getType(target) === "[object Map]") { result = new Map(); } else if (getType(target) === "[object Set]") { result = new Set(); } //解决循环引用 if (map[target]) { return map[target]; } map[target] = result; if (getType(target) === "[object Map]") { target.forEach((value, key) => { result.set(key, clone(value, map)); }); return result; } else if (getType(target) === "[object Set]") { target.forEach(value => { result.add(clone(value, map)); }); return result; } else if (getType(target) === "[object Object]" || getType(target) === "[object Array]") { const keys = getType(target) === "[object Array]" ? undefined : Object.keys(target); function callback(value, key) { if (keys) { // 如果keys存在则说明value是一个对象的key,不存在则说明key就是数组的下标。 key = value } result[key] = clone(target[key], map) } handleWhile(keys || target, callback) } else { result = handleOherData(target) } return result; } else { if (getType(target) === "[object Symbol]") { return cloneSymbol(target) } else { return target; } } } let map = {} const result = clone(target, map); map = null; return result } 在这里可以向面试官展示你的六个编程能力。 代码逻辑的严谨性。 深入了解数据类型的能力。 JS Api 的熟练使用的能力。 了解箭头函数和普通函数的区别。 熟练使用正则表达式的能力。 模块化开发的能力 王者段位 以上代码中还有很多数据类型的拷贝,没有实现,有兴趣的话可以在评论中实现一下,王者属于你哦! 总结 综上所述,面试官叫你实现一个深拷贝,其实是要考察你各方面的能力。例如 白银段位 对原始类型和引用类型数据的判断能力。 对递归思维的应用的能力。 黄金段位 正确理解引用类型概念的能力。 精确判断数据类型的能力。 铂金段位 对循环引用的理解,如何解决循环引用引起的问题的能力。 对内存泄露的认识和避免泄露的能力。 砖石段位 具有优化代码运行性能的能力。 了解遍历的效率的能力。 了解++i和i++的区别。 代码抽象的能力。 星耀段位 代码逻辑的严谨性。 深入了解数据类型的能力。 JS Api 的熟练使用的能力。 了解箭头函数和普通函数的区别。 熟练使用正则表达式的能力。 模块化开发的能力 所以不要去死记硬背一些手写代码的面试题,最好自己动手写一下,看看自己达到那个段位了。 最后 对于大厂面试,我最后想要强调的一点就是心态真的很重要,是决定你在面试过程中发挥的关键,若不能正常发挥,很可能就因为一个小失误与offer失之交臂,所以一定要重视起来。另外提醒一点,充分复习,是消除你紧张的心理状态的关键,但你复习充分了,自然面试过程中就要有底气得多。 我平时一直有整理面试题的习惯,有随时跳出舒适圈的准备,不知不觉整理了229页了,在这里分享给大家,有需要的点击这里免费领取题目+解析PDF 篇幅有限,仅展示部分内容 如果你需要这份完整版的面试题+解析,【点击我】就可以了。 希望大家明年的金三银四面试顺利,拿下自己心仪的offer!

优秀的个人博客,低调大师

Fuchsia 编程语言策略:前端用 Dart,Go 要退出

谷歌新一代开源操作系统Fuchsia 的开发者网站上公布了一份Fuchsia 编辑语言策略,文档描述了 C、C++、Dart、Rust 与 Go 的优劣势,并明确指定了其中哪些语言将会在 Fuchsia 开发生态中得到怎样程度的支持。 该策略文档描述了 Fuchsia 项目在 Fuchsia Platform Source Tree(Fuchsia 平台源码树)和供终端开发者在这之外构建 Fuchsia 开发目标设备软件时使用和支持的编程语言。 Fuchsia Platform Source Tree 是 fuchsia.googlesource.com 上托管的 Fuchsia 源码,终端开发者是指在 Fuchsia Platform Source Tree 之外为 Fuchsia 编写软件的人。与终端开发者相比,Fuchsia Platform Source Tree 可以汇聚对 Fuchsia 系统及其基础技术更大的更改,因为可以执行仅影响 Fuchsia Platform Source Tree 的更改,而无需与其他人员进行协调。相反,影响终端开发者的更改需要更多的协调,并且需要更长的执行时间。 谷歌介绍,对终端开发者的支持意味着 Fuchsia SDK 需要包含可帮助他们为 Fuchsia 开发软件的工具和库,包括 FIDL(FuchsiaInterface definition language,Fuchsia接口定义语言)特定语言后端与支持库等。对异步编程的强大支持则意味着可以使用直线型代码,例如使用 Rust/Dart 等语言的 async/await 编写异步程序。 文档接下来分别罗列了几种编程语言,分析了它们的优缺点之后,得出如下结论: C C 被支持用于终端开发者。 在 Fuchsia Platform Source Tree 中,不鼓励使用 C 的新用法,并且仅批准用于以下领域: 用于底层系统编程,包括内核中 用于定义到共享库和其它系统组件的 ABI 稳定接口 C++ C++ 被支持用于终端开发者。 批准在整个 Fuchsia Platform Source Tree 中使用 C++。 Dart Dart 被支持用于面向非驱动程序的终端开发者。 Dart 被批准在 Fuchsia Platform Source Tree 中用于用户界面和无限期运行的程序。 Rust 终端开发者不支持使用 Rust。 除了 Zircon 内核,Rust 被批准在整个 Fuchsia Platform Source Tree 中使用。 Go Go 仅被批准在网络技术栈中使用,因为网络栈的迁移需要大量成本。时间准许的话,应该将网络栈迁移到批准的语言。 必须将 Fuchsia 中用于目标设备上生产软件的所有其它Go 代码迁移到批准的语言。 总结一下,Go 在 Fuchsia 生态中可能会有些失落,值得注意的是文档中关于其缺点的介绍: 使用垃圾回收来管理内存,这比其它用于管理内存的技术占用更多的资源 运行时环境庞大 Fuchsia Platform Source Tree 在使用 Go 方面具有负面的实施经验。Fuchsia 项目在 Go 中构建的系统组件消耗的内存和内核资源相比使用 C++ 或 Rust 构建的同类组件更多 工具链产生大型二进制文件 值得关注的是,文档中没有涉及 Fuchsia已经确认支持的 Python。不过关于Python 的支持细节,支持语言页面也没有详细内容,只有一栏栏TODO,应该是还没有做好准备。 而另一方面,此前关于支持 JavaScript的传言,看来也无法得证。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册