首页 文章 精选 留言 我的

精选列表

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

前端面试常考题:JS垃圾回收机制

摘要:众所周知,应用程序在运行过程中需要占用一定的内存空间,且在运行过后就必须将不再用到的内存释放掉,否则就会出现下图中内存的占用持续升高的情况,一方面会影响程序的运行速度,另一方面严重的话则会导致整个程序的崩溃。 众所周知,应用程序在运行过程中需要占用一定的内存空间,且在运行过后就必须将不再用到的内存释放掉,否则就会出现下图中内存的占用持续升高的情况,一方面会影响程序的运行速度,另一方面严重的话则会导致整个程序的崩溃。 JavaScript中的内存管理 内存:由可读写单元组成,表示一片可操作空间; 管理:人为的去操作一片空间的申请、使用和释放; 内存管理:开发者主动申请空间、使用空间、释放空间; 管理流程:申请-使用-释放 部分语言需要(例如C语言)需要手动去释放内存,但是会很麻烦,所以很多语言,例如JAVA都会提供自动的内存管理机制,称为“垃圾回收机制”,JavaScript语言中也提供了垃圾回收机制(Garbage Collecation),简称GC机制。 全停顿(Stop The World ) 在介绍垃圾回收算法之前,我们先了解一下「全停顿」。垃圾回收算法在执行前,需要将应用逻辑暂停,执行完垃圾回收后再执行应用逻辑,这种行为称为 「全停顿」(Stop The World)。例如,如果一次GC需要50ms,应用逻辑就会暂停50ms。 全停顿的目的,是为了解决应用逻辑与垃圾回收器看到的情况不一致的问题。 举个例子,在自助餐厅吃饭,高高兴兴地取完食物回来时,结果发现自己餐具被服务员收走了。这里,服务员好比垃圾回收器,餐具就像是分配的对象,我们就是应用逻辑。在我们看来,只是将餐具临时放在桌上,但是服务员看来觉得你已经不需要使用了,因此就收走了。你与服务员对于同一个事物看到的情况是不一致,导致服务员做了与我们不期望的事情。因此,为避免应用逻辑与垃圾回收器看到的情况不一致,垃圾回收算法在执行时,需要停止应用逻辑。 JavaScript中的垃圾回收 JavaScript中会被判定为垃圾的情形如下: 对象不再被引用; 对象不能从根上访问到; GC算法 常见的GC算法如下: 引用计数 标记清除 标记整理 分代回收 引用计数 早期的浏览器最常使用的垃圾回收方法叫做"引用计数"(reference counting):语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。 const user1 = {age: 11} const user2 = {age: 22} const user3 = {age: 33} const userList = [user1.age, user2.age, user3.age] 上面这段代码,当执行过一遍过后,user1、user2、user3都是被userList引用的,所以它们的引用计数不为零,就不会被回收 function fn() { const num1 = 1 const num2 = 2 } fn() 上面代码中fn函数执行完毕,num1、num2都是局部变量,执行过后,它们的引用计数就都为零,所有这样的代码就会被当做“垃圾”,进行回收。 引用计数算法有一个比较大的问题:循环引用 function objGroup(obj1, obj2) { obj1.next = obj2 obj2.prev = obj1 return { o1: obj1, o2: obj2, } } let obj = objGroup({name: 'obj1'}, {name: 'obj2'}) console.log(obj) 上面的这个例子中,obj1和obj2通过各自的属性相互引用,所有它们的引用计数都不为零,这样就不会被垃圾回收机制回收,造成内存浪费。 引用计数算法其实还有一个比较大的缺点,就是我们需要单独拿出一片空间去维护每个变量的引用计数,这对于比较大的程序,空间开销还是比较大的。 引用计数算法优点: 引用计数为零时,发现垃圾立即回收; 最大限度减少程序暂停; 引用计数算法缺点: 无法回收循环引用的对象; 空间开销比较大; 标记清除(Mark-Sweep) 核心思想:分标记和清除两个阶段完成。 遍历所有对象找标记活动对象; 遍历所有对象清除没有标记对象; 回收相应的空间。 标记清除算法的优点是:对比引用计数算法,标记清除算法最大的优点是能够回收循环引用的对象,它也是v8引擎使用最多的算法。 标记清除算法的缺点是: 上图我们可以看到,红色区域是一个根对象,就是一个全局变量,会被标记;而蓝色区域就是没有被标记的对象,会被回收机制回收。这时就会出现一个问题,表面上蓝色区域被回收了三个空间,但是这三个空间是不连续的,当我们有一个需要三个空间的对象,那么我们刚刚被回收的空间是不能被分配的,这就是“空间碎片化”。 标记整理(Mark-Compact) 为了解决内存碎片化的问题,提高对内存的利用,引入了标记整理算法。 标记整理可以看做是标记清除的增强。标记阶段的操作和标记清除一致。 清除阶段会先执行整理,移动对象位置,将存活的对象移动到一边,然后再清理端边界外的内存。 标记整理的缺点是:移动对象位置,不会立即回收对象,回收的效率比较慢。 增量标记(Incremental Marking) 为了减少全停顿的时间,V8对标记进行了优化,将一次停顿进行的标记过程,分成了很多小步。每执行完一小步就让应用逻辑执行一会儿,这样交替多次后完成标记。 长时间的GC,会导致应用暂停和无响应,将会导致糟糕的用户体验。从2011年起,v8就将「全暂停」标记换成了增量标记。改进后的标记方式,最大停顿时间减少到原来的1/6。 v8引擎垃圾回收策略 采用分代回收的思想; 内存分为新生代、老生代; 针对不同对象采用不同算法: (1)新生代:对象的存活时间较短。新生对象或只经过一次垃圾回收的对象。 (2)老生代:对象存活时间较长。经历过一次或多次垃圾回收的对象。 V8堆的空间等于新生代空间加上老生代空间。且针对不同的操作系统对空间做了内存的限制。 针对浏览器来说,这样的内存是足够使用的。限制内存的原因: 针对浏览器的GC机制,经过不断的测试,如果内存再设置大一点,GC回收的时间就会达到用户的感知,会造成感知上的卡顿。 回收新生代对象 回收新生代对象主要采用复制算法(Scavenge 算法)加标记整理算法。而Scavenge 算法的具体实现,主要采用了Cheney算法。 Cheney算法将内存分为两个等大空间,使用空间为From,空闲空间为To。 检查From空间内的存活对象,若对象存活,检查对象是否符合晋升条件,若符合条件则晋升到老生代,否则将对象从From空间复制到To空间。若对象不存活,则释放不存活对象的空间。完成复制后,将From空间与To空间进行角色翻转。 对象晋升机制 一轮GC还存活的新生代需要晋升。 当对象从From空间复制到To空间时,若To空间使用超过 25%,则对象直接晋升到老生代中。设置为25%的比例的原因是,当完成Scavenge回收后,To空间将翻转成From空间,继续进行对象内存的分配。若占比过大,将影响后续内存分配。 回收老生代对象 回收老生代对象主要采用标记清除、标记整理、增量标记算法,主要使用标记清除算法,只有在内存分配不足时,采用标记整理算法。 首先使用标记清除完成垃圾空间的回收; 采用标记整理进行空间优化; 采用增量标记进行效率优化; 新生代和老生代回收对比 新生代由于占用空间比较少,采用空间换时间机制。 老生代区域空间比较大,不太适合大量的复制算法和标记整理,所以最常用的是标记清除算法,为了就是让全停顿的时间尽量减少。 内存泄漏识别方法 我们先写一段比较消耗内存的代码: <button class="btn">点击</button> <script> const btn = document.querySelector('.btn') const arrList = [] btn.onclick = function() { for(let i = 0; i < 100000; i++) { const p = document.createElement('p') // p.innerHTML = '我是一个p元素' document.body.appendChild(p) } arrList.push(new Array(1000000).join('x')) } </script> 使用浏览器的Performance来监控内存变化 点击录制,然后我们操作们感觉消耗性能的操作,操作完成之后,点击stop停止录制。 然后我们看一看是那些地方引起了内存的泄漏,我们只需要关注内存即可。 可以看到内存在短时间消耗的比较快,下降的小凹槽,就是浏览器在进行垃圾回收。 性能优化 1.避免使用全局变量 全局变量会挂载在window下; 全局变量至少有一个引用计数; 全局变量存活更久,持续占用内存; 在明确数据作用域的情况下,尽量使用局部变量; 2.减少判断层级 function doSomething(part, chapter) { const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node'] if (part) { if (parts.includes(part)) { console.log('属于当前课程') if (chapter > 5) { console.log('您需要提供 VIP 身份') } } } else { console.log('请确认模块信息') } } doSomething('Vue', 6) // 减少判断层级 function doSomething(part, chapter) { const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node'] if (!part) { console.log('请确认模块信息') return } if (!parts.includes(part)) return console.log('属于当前课程') if (chapter > 5) { console.log('您需要提供 VIP 身份') } } doSomething('Vue', 6) 3.减少数据读取次数 对于频繁使用的数据,我们要对数据进行缓存。 <div id="skip" class="skip"></div> <script> var oBox = document.getElementById('skip') // function hasEle (ele, cls) { // return ele.className === cls // } function hasEle (ele, cls) { const className = ele.className return className === cls } console.log(hasEle(oBox, 'skip')) </script> 4.减少循环体中的活动 var test = () => { var i var arr = ['Hello World!', 25, '岂曰无衣,与子同袍'] for(i = 0; i < arr.length; i++) { console.log(arr[i]) } } // 优化后,将arr.length单独提出,防止每次循环都获取一次 var test = () => { var i var arr = ['Hello World!', 25, '岂曰无衣,与子同袍'] var len = arr.length for(i = 0; i < len; i++) { console.log(arr[i]) } } 5.事件绑定优化 <ul class="ul"> <li>Hello World!</li> <li>25</li> <li>岂曰无衣,与子同袍</li> </ul> <script> var list = document.querySelectorAll('li') function showTxt(ev) { console.log(ev.target.innerHTML) } for (item of list) { item.onclick = showTxt } // 优化后 function showTxt(ev) { var target = ev.target if (target.nodeName.toLowerCase() === 'li') { console.log(ev.target.innerHTML) } } var ul = document.querySelector('.ul') ul.addEventListener('click', showTxt) </script> 6.避开闭包陷阱 <button class="btn">点击</button> <script> function foo() { let el = document.querySelector('.btn') el.onclick = function() { console.log(el.className) } } foo() // 优化后 function foo1() { let el = document.querySelector('.btn') el.onclick = function() { console.log(el.className) } el = null // 将el置为 null 防止闭包中的引用使得不能被回收 } foo1() </script> 本文分享自华为云社区《Vue进阶(幺陆玖):JS垃圾回收机制》,原文作者:SHQ5785 。 点击关注,第一时间了解华为云新鲜技术~

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

我在阿里云做前端代码智能化

作为一个整天以代码为伴的码农,避免不了会接触到各种代码提示工具,但是呢,用久了之后会发现他们都有个共同点,那就是模型巨大,动辄几百兆;并且模型大必然需要更多的计算,同样会导致电脑内存占用高,风扇呼呼的转,时间久了逐渐会发现电脑存储不够用了,电脑变卡了等等问题。 那么,有没有一款轻量化的代码提示插件?或者说,如何实现一款轻量化的代码提示插件呢? 下面我会从 模型选择、模型实现、模型优化三个方面 来介绍我们在代码智能提示方面的一些实践。 模型选择 如何衡量一个模型的效果是好还是不好呢? 我们首先建立了基本的模型衡量体系,见下图。 我们会从推荐的准确性、完整性、连续性、性能、个性化以及智能性六个维度去综合评价一个模型的优劣。之后,我们会进一步完善这个体系,使之能够系统完整的评测模型的效果。 实践中,我们选取了 GPT-2模型和基于markov的n-gram统计模型进行对比。 对比发现,GPT-2模型在准确性和完整性方面表现优异,但是在性能方面,由于模型较大,推荐一次耗时较久,(这里我们试了最少参数的版本,训练之后模型在500M左右,推荐一次大概需要10S),由于暂时没找到模型压缩的方法,只能暂时放弃。 另一个n-gram模型,在测试后发现,它在持续推荐和性能方面表现优异,模型大小仅有40M,但也并非完美,在准确性和完整性方面表现的不是很好。 在实践中,我们发现,推荐耗时在毫秒级别能够使用户顺畅无阻碍的编写下去,多于1s,则会让用户的输入产生停顿。 基于此我们暂时选取n-gram作为我们的推荐模型。 模型实现 下面介绍一下n-gram模型的基本原理以及我们的实现。 首先,n-gram模型基于马尔可夫链的假设,即:当前这个词出现的概率仅仅跟前面几个有限的词相关。 以最简单的n=2为例,即 下一个词出现的概率仅跟之前一个词相关,基于这个思想,我们将大量代码进行切分,这样我们得到了很多的二元组,这里可以使用单词的出现次数代表概率。这样,我们根据一个词就可以得到一个不同概率分布的推荐列表,然后每次都以当前词进行推荐,就可以产生持续不断的推荐了。切分的效果参考这张图 以下是模型训练和产生推荐的大致流程。 模型优化 在真实应用中,仅仅有这些是远远不够的。模型只是其中一部分,更多的应该是在应用过程中,依据实践经验和用户的反馈,对模型进行不断调优: 比如,在实际应用中,基于同一个上下文,我们一般会同时产生基于n=3,n=4等等不通粒度的推荐,我们会给不通粒度赋予不同的权重,然后结合概率进行筛选和排序,并且根据实际效果以及我们的反馈体系,对权重进行断优化,确保产生最佳效果的推荐。 另外,为了让推荐更加个性化,我们的模型需要具备实时训练的能力,即用户的输入在产生推荐的同时,还要参与模型的增量训练,并根据用户实际的选择对模型进行调优,确保模型的准确性和个性化,大致流程图如下 还有,为了得到更加轻量的模型,我们对语料库也进行了精简,比如,在初始模型中,我们对出现概率极低的语料进行了删减,保持模型的轻巧和高效,同时由于具备实时训练的能力,确保这个操作不会影响到推荐的准确率。 结语 当然,目前的这些对于一个轻巧好用的代码提示来说是远远不够的,当前我们正在尝试完善模型的衡量体系,并且在尝试更多轻量的语言模型,希望给用户提供完美的体验。 原文链接 本文为阿里云原创内容,未经允许不得转载。

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

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

前言 深拷贝这个功能在开发中经常使用到,特别在对引用类型的数据进行操作时,一般会先深拷贝一份赋值给一个变量,然后在对其操作,防止影响到其它使用该数据的地方。 如何实现一个深拷贝,在面试中出现频率一直居高不下。因为在实现一个深拷贝过程中,可以看出应聘者很多方面的能力。 本专栏将从青铜到王者来介绍怎么实现一个深拷贝,以及每个段位对应的能力。 青铜段位 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!

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

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

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册