简单1从输入一个URL地址到浏览器完成渲染的整个过程 这个问题属于老生常谈的经典问题了下面给出面试简单版作答浏览器地址栏输入URL并回车浏览器查找当前URL是否存在缓存,并比较缓存是否过期DNS解析URL对应的IP根据IP建立TCP连接(三次握手)发送http请求服务器处理请求,浏览器接受HTTP响应浏览器解析并渲染页面关闭TCP连接(四次握手) 注意!面试官可以基于这道题进行前端很多知识点的考察从http网络知识到浏览器原理再到前端性能优化这个题目都可以作为引子开始2什么是事件代理(事件委托)有什么好处 事件委托的原理:不给每个子节点单独设置事件监听器,而是设置在其父节点上,然后利用冒泡原理设置每个子节点。 优点:减少内存消耗和dom操作,提高性能在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的操作dom,那么引起浏览器重绘和回流的可能也就越多,页面交互的事件也就变的越长,这也就是为什么要减少dom操作的原因。每一个事件处理函数,都是一个对象,多一个事件处理函数,内存中就会被多占用一部分空间。如果要用事件委托,就会将所有的操作放到js程序里面,只对它的父级进行操作,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;动态绑定事件因为事件绑定在父级元素所以新增的元素也能触发同样的事件3addEventListener默认是捕获还是冒泡 默认是冒泡 addEventListener第三个参数默认为false代表执行事件冒泡行为。 当为true时执行事件捕获行为。4css的渲染层合成是什么浏览器如何创建新的渲染层 在DOM树中每个节点都会对应一个渲染对象(RenderObject),当它们的渲染对象处于相同的坐标空间(z轴空间)时,就会形成一个RenderLayers,也就是渲染层。渲染层将保证页面元素以正确的顺序堆叠,这时候就会出现层合成(composite),从而正确处理透明元素和重叠元素的显示。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。 浏览器如何创建新的渲染层根元素document有明确的定位属性(relative、fixed、sticky、absolute)opacity1有CSSfliter属性有CSSmask属性有CSSmixblendmode属性且值不为normal有CSStransform属性且值不为nonebackfacevisibility属性为hidden有CSSreflection属性有CSScolumncount属性且值不为auto或者有CSScolumnwidth属性且值不为auto当前有对于opacity、transform、fliter、backdropfilter应用动画overflow不为visible 注意!不少人会将这些合成层的条件和渲染层产生的条件混淆,这两种条件发生在两个不同的层处理环节,是完全不一样的具体可以看看这篇文章浏览器层合成与页面渲染优化5webpackPlugin和Loader的区别Loader:用于对模块源码的转换,loader描述了webpack如何处理非javascript模块,并且在buld中引入这些依赖。loader可以将文件从不同的语言(如TypeScript)转换为JavaScript,或者将内联图像转换为dataURL。比如说:CSSLoader,StyleLoader等。Plugin目的在于解决loader无法实现的其他事,它直接作用于webpack,扩展了它的功能。在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。6。applycallbind区别三者都可以改变函数的this对象指向。三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。bind是返回绑定this之后的函数,便于稍后调用;apply、call则是立即执行。bind()会返回一个新的函数,如果这个返回的新的函数作为构造函数创建一个新的对象,那么此时this不再指向传入给bind的第一个参数,而是指向用new创建的实例7举出闭包实际场景运用的例子 比如常见的防抖节流防抖functiondebounce(fn,delay300){lettimer;闭包引用的外界变量returnfunction(){constargsarguments;if(timer){clearTimeout(timer);}timersetTimeout((){fn。apply(this,args);},delay);};} 使用闭包可以在JavaScript中模拟块级作用域functionoutputNumbers(count){(function(){for(vari0;icount;i){alert(i);}})();alert(i);导致一个错误!} 闭包可以用于在对象中创建私有变量varaaa(function(){vara1;functionbbb(){a;console。log(a);}functionccc(){a;console。log(a);}return{b:bbb,json结构c:ccc,};})();console。log(aaa。a);undefinedaaa。b();2aaa。c();38css优先级是怎么计算的第一优先级:!important会覆盖页面内任何位置的元素样式1。内联样式,如stylecolor:green,权值为10002。ID选择器,如app,权值为01003。类、伪类、属性选择器,如。foo,:firstchild,p〔classfoo〕,权值为00104。标签、伪元素选择器,如p::firstline,权值为00015。通配符、子类选择器、兄弟选择器,如,,,权值为00006。继承的样式没有权值9事件循环相关题目必考(一般是代码输出顺序判断)setTimeout(function(){console。log(1);},0);asyncfunctionasync1(){console。log(2);constdataawaitasync2();console。log(3);returndata;}asyncfunctionasync2(){returnnewPromise((resolve){console。log(4);resolve(async2的结果);})。then((data){console。log(5);returndata;});}async1()。then((data){console。log(6);console。log(data);});newPromise(function(resolve){console。log(7);resolve()})。then(function(){console。log(8);}); 输出结果:247536async2的结果110http状态码204301302304400401403404含义http状态码204(无内容)服务器成功处理了请求,但没有返回任何内容http状态码301(永久移动)请求的网页已永久移动到新位置。服务器返回此响应(对GET或HEAD请求的响应)时,会自动将请求者转到新位置。http状态码302(临时移动)服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。http状态码304(未修改)自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。http状态码400(错误请求)服务器不理解请求的语法(一般为参数错误)。http状态码401(未授权)请求要求身份验证。对于需要登录的网页,服务器可能返回此响应。http状态码403(禁止)服务器拒绝请求。(一般为客户端的用户权限不够)http状态码404(未找到)服务器找不到请求的网页。11http2。0做了哪些改进3。0呢 http2。0特性如下二进制分帧传输多路复用头部压缩服务器推送 Http3。0相对于Http2。0是一种脱胎换骨的改变! http协议是应用层协议,都是建立在传输层之上的。我们也都知道传输层上面不只有TCP协议,还有另外一个强大的协议UDP协议,2。0和1。0都是基于TCP的,因此都会有TCP带来的硬伤以及局限性。而Http3。0则是建立在UDP的基础上。所以其与Http2。0之间有质的不同。 http3。0特性如下连接迁移无队头阻塞自定义的拥塞控制前向安全和前向纠错12position有哪些值,作用分别是什么staticstatic(没有定位)是position的默认值,元素处于正常的文档流中,会忽略left、top、right、bottom和zindex属性。relativerelative(相对定位)是指给元素设置相对于原本位置的定位,元素并不脱离文档流,因此元素原本的位置会被保留,其他的元素位置不会受到影响。使用场景:子元素相对于父元素进行定位absoluteabsolute(绝对定位)是指给元素设置绝对的定位,相对定位的对象可以分为两种情况:设置了absolute的元素如果存在有祖先元素设置了position属性为relative或者absolute,则这时元素的定位对象为此已设置position属性的祖先元素。如果并没有设置了position属性的祖先元素,则此时相对于body进行定位。使用场景:跟随图标图标使用不依赖定位父级的absolute和margin属性进行定位,这样,当文本的字符个数改变时,图标的位置可以自适应fixed可以简单说fixed是特殊版的absolute,fixed元素总是相对于body定位的。使用场景:侧边栏或者广告图inherit继承父元素的position属性,但需要注意的是IE8以及往前的版本都不支持inherit属性。sticky设置了sticky的元素,在屏幕范围(viewport)时该元素的位置并不受到定位影响(设置是top、left等属性无效),当该元素的位置将要移出偏移范围时,定位又会变成fixed,根据设置的left、top等属性成固定位置的效果。当元素在容器中被滚动超过指定的偏移值时,元素在容器内固定在指定位置。亦即如果你设置了top:50px,那么在sticky元素到达距离相对定位的元素顶部50px的位置时固定,不再向上移动(相当于此时fixed定位)。使用场景:跟随窗口13垂直水平居中实现方式 这道题基本也是css经典题目但是网上已经有太多千篇一律的答案了如果大家想在这道题加分 可以针对定宽高和不定宽高的实现多种不同的方案 建议大家直接看面试官:你能实现多少种水平垂直居中的布局(定宽高和不定宽高)14vue组件通讯方式有哪些方法props和emit父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过emit触发事件来做到的parent,children获取当前组件的父组件和当前组件的子组件attrs和listenersABC。Vue2。4开始提供了attrs和listeners来解决这个问题父组件中通过provide来提供变量,然后在子组件中通过inject来注入变量。(官方不推荐在实际业务中使用,但是写组件库时很常用)refs获取组件实例envetBus兄弟组件数据传递这种情况下可以使用事件总线的方式vuex状态管理15Vue响应式原理 整体思路是数据劫持观察者模式 对象内部通过defineReactive方法,使用Object。defineProperty将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。当页面使用对应属性时,每个属性都拥有自己的dep属性,存放他所依赖的watcher(依赖收集),当属性变化后会通知自己对应的watcher去更新(派发更新)。 相关代码如下classObserver{观测值constructor(value){this。walk(value);}walk(data){对象上的所有属性依次进行观测letkeysObject。keys(data);for(leti0;ikeys。length;i){letkeykeys〔i〕;letvaluedata〔key〕;defineReactive(data,key,value);}}}Object。defineProperty数据劫持核心兼容性在ie9以及以上functiondefineReactive(data,key,value){observe(value);递归关键如果value还是一个对象会继续走一遍odefineReactive层层遍历一直到value不是对象才停止思考?如果Vue数据嵌套层级过深性能会受影响Object。defineProperty(data,key,{get(){console。log(获取值);需要做依赖收集过程这里代码没写出来returnvalue;},set(newValue){if(newValuevalue)return;console。log(设置值);需要做派发更新过程这里代码没写出来valuenewValue;},});}exportfunctionobserve(value){如果传过来的是对象或者数组进行属性劫持if(Object。prototype。toString。call(value)〔objectObject〕Array。isArray(value)){returnnewObserver(value);}} 响应式数据原理详解传送门16VuenextTick原理 nextTick中的回调是在下次DOM更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。主要思路就是采用微任务优先的方式调用异步方法去执行nextTick包装的方法 相关代码如下letcallbacks〔〕;letpendingfalse;functionflushCallbacks(){pendingfalse;把标志还原为false依次执行回调for(leti0;icallbacks。length;i){callbacks〔i〕();}}lettimerFunc;定义异步方法采用优雅降级if(typeofPromise!undefined){如果支持promiseconstpPromise。resolve();timerFunc(){p。then(flushCallbacks);};}elseif(typeofMutationObserver!undefined){MutationObserver主要是监听dom变化也是一个异步方法letcounter1;constobservernewMutationObserver(flushCallbacks);consttextNodedocument。createTextNode(String(counter));observer。observe(textNode,{characterData:true,});timerFunc(){counter(counter1)2;textNode。dataString(counter);};}elseif(typeofsetImmediate!undefined){如果前面都不支持判断setImmediatetimerFunc(){setImmediate(flushCallbacks);};}else{最后降级采用setTimeouttimerFunc(){setTimeout(flushCallbacks,0);};}exportfunctionnextTick(cb){除了渲染watcher还有用户自己手动调用的nextTick一起被收集到数组callbacks。push(cb);if(!pending){如果多次调用nextTick只会执行一次异步等异步队列清空之后再把标志变为falsependingtrue;timerFunc();}} nextTick原理详解传送门17Vuediff原理 建议直接看diff算法详解传送门18路由原理history和hash两种路由方式的特点 hash模式location。hash的值实际就是URL中后面的东西它的特点在于:hash虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。可以为hash的改变添加监听事件window。addEventListener(hashchange,funcRef,false); 每一次改变hash(window。location。hash),都会在浏览器的访问历史中增加一个记录利用hash的以上特点,就可以来实现前端路由更新视图但不重新请求页面的功能了 特点:兼容性好但是不美观 history模式 利用了HTML5HistoryInterface中新增的pushState()和replaceState()方法。 这两个方法应用于浏览器的历史记录站,在当前已有的back、forward、go的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器不会刷新页面,这就为单页应用前端路由更新视图但不重新请求页面提供了基础。 特点:虽然美观,但是刷新会出现404需要后端进行配置19手写bindbind实现要复杂一点因为他考虑的情况比较多还要涉及到参数合并(类似函数柯里化)Function。prototype。myBindfunction(context,。。。args){if(!contextcontextnull){contextwindow;}创造唯一的key值作为我们构造的context内部方法名letfnSymbol();context〔fn〕this;letthisthis;bind情况要复杂一点constresultfunction(。。。innerArgs){第一种情况:若是将bind绑定之后的函数当作构造函数,通过new操作符使用,则不绑定传入的this,而是将this指向实例化出来的对象此时由于new操作符作用this指向result实例对象而result又继承自传入的this根据原型链知识可得出以下结论this。protoresult。prototypethisinstanceofresulttruethis。proto。protoresult。prototype。protothis。prototype;thisinstanceofthistrueif(thisinstanceofthistrue){此时this指向指向result的实例这时候不需要改变this指向this〔fn〕this;this〔fn〕(。。。〔。。。args,。。。innerArgs〕);这里使用es6的方法让bind支持参数合并deletethis〔fn〕;}else{如果只是作为普通函数调用那就很简单了直接改变this指向为传入的contextcontext〔fn〕(。。。〔。。。args,。。。innerArgs〕);deletecontext〔fn〕;}};如果绑定的是构造函数那么需要继承构造函数原型属性和方法实现继承的方式:使用Object。createresult。prototypeObject。create(this。prototype);returnresult;};用法如下functionPerson(name,age){console。log(name);我是参数传进来的nameconsole。log(age);我是参数传进来的ageconsole。log(this);构造函数this指向实例对象}构造函数原型的方法Person。prototype。sayfunction(){console。log(123);}letobj{objName:我是obj传进来的name,objAge:我是obj传进来的age}普通函数functionnormalFun(name,age){console。log(name);我是参数传进来的nameconsole。log(age);我是参数传进来的ageconsole。log(this);普通函数this指向绑定bind的第一个参数也就是例子中的objconsole。log(this。objName);我是obj传进来的nameconsole。log(this。objAge);我是obj传进来的age}先测试作为构造函数调用letbindFunPerson。myBind(obj,我是参数传进来的name)letanewbindFun(我是参数传进来的age)a。say()123再测试作为普通函数调用letbindFunnormalFun。myBind(obj,我是参数传进来的name)bindFun(我是参数传进来的age)19手写promise。all和race(京东)静态方法staticall(promiseArr){letresult〔〕;声明一个计数器每一个promise返回就加一letcount0;returnnewMypromise((resolve,reject){for(leti0;ipromiseArr。length;i){这里用Promise。resolve包装一下防止不是Promise类型传进来Promise。resolve(promiseArr〔i〕)。then((res){这里不能直接push数组因为要控制顺序一一对应(感谢评论区指正)result〔i〕res;count;只有全部的promise执行成功之后才resolve出去if(countpromiseArr。length){resolve(result);}},(err){reject(err);});}});}静态方法staticrace(promiseArr){returnnewMypromise((resolve,reject){for(leti0;ipromiseArr。length;i){Promise。resolve(promiseArr〔i〕)。then((res){promise数组只要有任何一个promise状态变更就可以返回resolve(res);},(err){reject(err);});}});}}20手写实现一个寄生组合继承functionParent(name){this。namename;this。say(){console。log(111);};}Parent。prototype。play(){console。log(222);};functionChildren(name){Parent。call(this);this。namename;}Children。prototypeObject。create(Parent。prototype);Children。prototype。constructorChildren;letchildnewChildren(111);console。log(child。name);child。say();child。play();21手写new操作符functionmyNew(fn,。。。args){letobjObject。create(fn。prototype);letresfn。call(obj,。。。args);if(res(typeofresobjecttypeofresfunction)){returnres;}returnobj;}用法如下:functionPerson(name,age){this。namename;this。ageage;}Person。prototype。sayfunction(){console。log(this。age);};letp1myNew(Person,lihua,18);console。log(p1。name);console。log(p1);p1。say();22手写setTimeout模拟实现setInterval(阿里)functionmySetInterval(fn,time1000){lettimernull,isClearfalse;functioninterval(){if(isClear){isClearfalse;clearTimeout(timer);return;}fn();timersetTimeout(interval,time);}timersetTimeout(interval,time);return(){isCleartrue;};}letamySettimeout((){console。log(111);},1000)letcancelmySettimeout((){console。log(222)},1000)cancel()23手写发布订阅模式(字节)classEventEmitter{constructor(){this。events{};}实现订阅on(type,callBack){if(!this。events〔type〕){this。events〔type〕〔callBack〕;}else{this。events〔type〕。push(callBack);}}删除订阅off(type,callBack){if(!this。events〔type〕)return;this。events〔type〕this。events〔type〕。filter((item){returnitem!callBack;});}只执行一次订阅事件once(type,callBack){functionfn(){callBack();this。off(type,fn);}this。on(type,fn);}触发事件emit(type,。。。rest){this。events〔type〕this。events〔type〕。forEach((fn)fn。apply(this,rest));}}使用如下consteventnewEventEmitter();consthandle(。。。rest){console。log(rest);};event。on(click,handle);event。emit(click,1,2,3,4);event。off(click,handle);event。emit(click,1,2);event。once(dbClick,(){console。log(123456);});event。emit(dbClick);event。emit(dbClick);24手写防抖节流(京东)防抖functiondebounce(fn,delay300){默认300毫秒lettimer;returnfunction(){constargsarguments;if(timer){clearTimeout(timer);}timersetTimeout((){fn。apply(this,args);改变this指向为调用debounce所指的对象},delay);};}window。addEventListener(scroll,debounce((){console。log(111);},1000));节流设置一个标志functionthrottle(fn,delay){letflagtrue;return(){if(!flag)return;flagfalse;timersetTimeout((){fn();flagtrue;},delay);};}window。addEventListener(scroll,throttle((){console。log(111);},1000));25手写将虚拟Dom转化为真实Dom(类似的递归题必考){tag:DIV,attrs:{id:app},children:〔{tag:SPAN,children:〔{tag:A,children:〔〕}〕},{tag:SPAN,children:〔{tag:A,children:〔〕},{tag:A,children:〔〕}〕}〕}把上诉虚拟Dom转化成下方真实Domspanspanspanspan 答案真正的渲染函数functionrender(vnode){如果是数字类型转化为字符串if(typeofvnodenumber){vnodeString(vnode);}字符串类型直接就是文本节点if(typeofvnodestring){returndocument。createTextNode(vnode);}普通DOMconstdomdocument。createElement(vnode。tag);if(vnode。attrs){遍历属性Object。keys(vnode。attrs)。forEach((key){constvaluevnode。attrs〔key〕;dom。setAttribute(key,value);});}子数组进行递归操作这一步是关键vnode。children。forEach((child)dom。appendChild(render(child)));returndom;}26手写实现一个对象的flatten方法(阿里) 题目描述constobj{a:{b:1,c:2,d:{e:5}},b:〔1,3,{a:2,b:3}〕,c:3}flatten(obj)结果返回如下{a。b:1,a。c:2,a。d。e:5,b〔0〕:1,b〔1〕:3,b〔2〕。a:2,b〔2〕。b:3c:3} 答案functionisObject(val){returntypeofvalobjectval!null;}functionflatten(obj){if(!isObject(obj)){return;}letres{};constdfs(cur,prefix){if(isObject(cur)){if(Array。isArray(cur)){cur。forEach((item,index){dfs(item,{prefix}〔{index}〕);});}else{for(letkincur){dfs(cur〔k〕,{prefix}{prefix?。:}{k});}}}else{res〔prefix〕cur;}};dfs(obj,);returnres;}flatten();27手写判断括号字符串是否有效(小米) 题目描述给定一个只包括(,),{,},〔,〕的字符串s,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。示例1:输入:s()输出:true示例2:输入:s()〔〕{}输出:true示例3:输入:s(〕输出:false 答案constisValidfunction(s){if(s。length21){returnfalse;}constregObj{{:},(:),〔:〕,};letstack〔〕;for(leti0;is。length;i){if(s〔i〕{s〔i〕(s〔i〕〔){stack。push(s〔i〕);}else{constcurstack。pop();if(s〔i〕!regObj〔cur〕){returnfalse;}}}if(stack。length){returnfalse;}returntrue;};28手写查找数组公共前缀(美团) 题目描述编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串。示例1:输入:strs〔flower,flow,flight〕输出:fl示例2:输入:strs〔dog,racecar,car〕输出:解释:输入不存在公共前缀。 答案constlongestCommonPrefixfunction(strs){conststrstrs〔0〕;letindex0;while(indexstr。length){conststrCurstr。slice(0,index1);for(leti0;istrs。length;i){if(!strs〔i〕!strs〔i〕。startsWith(strCur)){returnstr。slice(0,index);}}index;}returnstr;};29手写字符串最长的不重复子串 题目描述给定一个字符串s,请你找出其中不含有重复字符的最长子串的长度。示例1:输入:sabcabcbb输出:3解释:因为无重复字符的最长子串是abc,所以其长度为3。示例2:输入:sbbbbb输出:1解释:因为无重复字符的最长子串是b,所以其长度为1。示例3:输入:spwwkew输出:3解释:因为无重复字符的最长子串是wke,所以其长度为3。请注意,你的答案必须是子串的长度,pwke是一个子序列,不是子串。示例4:输入:s输出:0 答案constlengthOfLongestSubstringfunction(s){if(s。length0){return0;}letleft0;letright1;letmax0;while(rights。length){letlrs。slice(left,right);constindexlr。indexOf(s〔right〕);if(index1){leftindexleft1;}else{lrs。slice(left,right1);maxMath。max(max,lr。length);}right;}returnmax;};30手写如何找到数组中第一个没出现的最小正整数怎么优化(字节)给你一个未排序的整数数组nums,请你找出其中没有出现的最小的正整数。请你实现时间复杂度为O(n)并且只使用常数级别额外空间的解决方案。示例1:输入:nums〔1,2,0〕输出:3示例2:输入:nums〔3,4,1,1〕输出:2示例3:输入:nums〔7,8,9,11,12〕输出:1 这是一道字节的算法题目的在于不断地去优化算法思路第一版O(n2)的方法constfirstMissingPositive(nums){leti0;letres1;while(inums。length){if(nums〔i〕res){res;i0;}else{i;}}returnres;};第二版时间空间均为O(n)constfirstMissingPositive(nums){constsetnewSet();for(leti0;inums。length;i){set。add(nums〔i〕);}for(leti1;inums。length1;i){if(!set。has(i)){returni;}}};最终版时间复杂度为O(n)并且只使用常数级别空间constfirstMissingPositive(nums){for(leti0;inums。length;i){while(nums〔i〕1nums〔i〕nums。length对1nums。length范围内的元素进行安排nums〔nums〔i〕1〕!nums〔i〕已经出现在理想位置的,就不用交换){consttempnums〔nums〔i〕1〕;交换nums〔nums〔i〕1〕nums〔i〕;nums〔i〕temp;}}现在期待的是〔1,2,3,。。。〕,如果遍历到不是放着该放的元素for(leti0;inums。length;i){if(nums〔i〕!i1){returni1;}}returnnums。length1;发现元素1nums。length占满了数组,一个没缺};31手写怎么在制定数据源里面生成一个长度为n的不重复随机数组能有几种方法时间复杂度多少(字节)第一版时间复杂度为O(n2)functiongetTenNum(testArray,n){letresult〔〕;for(leti0;in;i){constrandomMath。floor(Math。random()testArray。length);constcurtestArray〔random〕;if(result。includes(cur)){i;break;}result。push(cur);}returnresult;}consttestArray〔1,2,3,4,5,6,7,8,9,10,11,12,13,14〕;constresArrgetTenNum(testArray,10);第二版标记法自定义属性法时间复杂度为O(n)functiongetTenNum(testArray,n){lethash{};letresult〔〕;letranNumn;while(ranNum0){constranMath。floor(Math。random()testArray。length);if(!hash〔ran〕){hash〔ran〕true;result。push(ran);ranNum;}}returnresult;}consttestArray〔1,2,3,4,5,6,7,8,9,10,11,12,13,14〕;constresArrgetTenNum(testArray,10);第三版交换法时间复杂度为O(n)functiongetTenNum(testArray,n){constcloneArr〔。。。testArray〕;letresult〔〕;for(leti0;in;i){debugger;constranMath。floor(Math。random()(cloneArr。lengthi));result。push(cloneArr〔ran〕);cloneArr〔ran〕cloneArr〔cloneArr。lengthi1〕;}returnresult;}consttestArray〔1,2,3,4,5,6,7,8,9,10,11,12,13,14〕;constresArrgetTenNum(testArray,14); 值得一提的是操作数组的时候使用交换法这种思路在算法里面很常见最终版边遍历边删除时间复杂度为O(n)functiongetTenNum(testArray,n){constcloneArr〔。。。testArray〕;letresult〔〕;for(leti0;in;i){constrandomMath。floor(Math。random()cloneArr。length);constcurcloneArr〔random〕;result。push(cur);cloneArr。splice(random,1);}returnresult;}consttestArray〔1,2,3,4,5,6,7,8,9,10,11,12,13,14〕;constresArrgetTenNum(testArray,14);中等1Webpack有哪些优化手段 随着项目越来越大,Webpack构建速度可能会越来越慢,构建出来的js的体积也越来越大,此时就需要对Webpack的配置进行优化 这个知识点可以单独开一篇文章大家请看带你深度解锁Webpack系列(优化篇)2css怎么开启硬件加速(GPU加速) 浏览器在处理下面的css的时候,会使用GPU渲染transform(当3D变换的样式出现时会使用GPU加速)opacityfilterwillchange采用transform:translateZ(0)采用transform:translate3d(0,0,0)使用CSS的willchange属性。willchange可以设置为opacity、transform、top、left、bottom、right。 注意!层爆炸,由于某些原因可能导致产生大量不在预期内的合成层,虽然有浏览器的层压缩机制,但是也有很多无法进行压缩的情况,这就可能出现层爆炸的现象(简单理解就是,很多不需要提升为合成层的元素因为某些不当操作成为了合成层)。解决层爆炸的问题,最佳方案是打破overlap的条件,也就是说让其他元素不要和合成层元素重叠。简单直接的方式:使用3D硬件加速提升动画性能时,最好给元素增加一个zindex属性,人为干扰合成的排序,可以有效减少创建不必要的合成层,提升渲染性能,移动端优化效果尤为明显。3常用设计模式有哪些并举例使用场景 1。工厂模式传入参数即可创建实例 虚拟DOM根据参数的不同返回基础标签的Vnode和组件Vnode 2。单例模式整个程序有且仅有一个实例 vuex和vuerouter的插件注册方法install判断如果系统存在实例就直接返回掉 3。发布订阅模式(vue事件机制) 4。观察者模式(响应式数据原理) 5。装饰模式:(装饰器的用法) 6。策略模式策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案比如选项的合并策略 。。。其他模式欢迎补充4浏览器缓存策略是怎样的(强缓存协商缓存)具体是什么过程? 这个也是经典的前端缓存问题知识点加起来是一篇文章了5https加密过程是怎样的 使用了对称加密可非对称加密的混合方式6flex:1是哪些属性组成的 flex实际上是flexgrow、flexshrink和flexbasis三个属性的缩写。 flexgrow:定义项目的的放大比例;默认为0,即即使存在剩余空间,也不会放大;所有项目的flexgrow为1:等分剩余空间(自动放大占位);flexgrow为n的项目,占据的空间(放大的比例)是flexgrow为1的n倍。 flexshrink:定义项目的缩小比例;默认为1,即如果空间不足,该项目将缩小;所有项目的flexshrink为1:当空间不足时,缩小的比例相同;flexshrink为0:空间不足时,该项目不会缩小;flexshrink为n的项目,空间不足时缩小的比例是flexshrink为1的n倍。 flexbasis:定义在分配多余空间之前,项目占据的主轴空间(mainsize),浏览器根据此属性计算主轴是否有多余空间默认值为auto,即项目原本大小;设置后项目将占据固定空间。7304是什么意思一般什么场景出现,命中强缓存返回什么状态码 协商缓存命中返回304 这种方式使用到了headers请求头里的两个字段,LastModifiedIfModifiedSince。服务器通过响应头LastModified告知浏览器,资源最后被修改的时间: LastModified:Thu,20Jun201915:58:05GMT 当再次请求该资源时,浏览器需要再次向服务器确认,资源是否过期,其中的凭证就是请求头IfModifiedSince字段,值为上次请求中响应头LastModified字段的值: IfModifiedSince:Thu,20Jun201915:58:05GMT 浏览器在发送请求的时候服务器会检查请求头requestheader里面的IfmodifiedSince,如果最后修改时间相同则返回304,否则给返回头(responseheader)添加lastModified并且返回数据(responsebody)。 另外,浏览器在发送请求的时候服务器会检查请求头(requestheader)里面的ifnonematch的值与当前文件的内容通过hash算法(例如nodejs:cryto。createHash(sha1))生成的内容摘要字符对比,相同则直接返回304,否则给返回头(responseheader)添加etag属性为当前的内容摘要字符,并且返回内容。 综上总结为:请求头lastmodified的日期与响应头的lastmodified一致请求头ifnonematch的hash与响应头的etag一致这两种情况会返回StatusCode:304 强缓存命中返回200200(fromcache)8手写Vue。extend实现srcglobalapiinitExtend。jsimport{mergeOptions}from。。utilindex;exportdefaultfunctioninitExtend(Vue){letcid0;组件的唯一标识创建子类继承Vue父类便于属性扩展Vue。extendfunction(extendOptions){创建子类的构造函数并且调用初始化方法constSubfunctionVueComponent(options){this。init(options);调用Vue初始化方法};Sub。cidcid;Sub。prototypeObject。create(this。prototype);子类原型指向父类Sub。prototype。constructorSub;constructor指向自己Sub。optionsmergeOptions(this。options,extendOptions);合并自己的options和父类的optionsreturnSub;};}9vuerouter中路由方法pushState和replaceState能否触发popSate事件 答案是:不能 pushState和replaceState HTML5新接口,可以改变网址(存在跨域限制)而不刷新页面,这个强大的特性后来用到了单页面应用如:vuerouter,reactrouterdom中。 注意:仅改变网址,网页不会真的跳转,也不会获取到新的内容,本质上网页还停留在原页面window。history。pushState(state,title,targetURL);状态对象:传给目标路由的信息,可为空页面标题:目前所有浏览器都不支持,填空字符串即可可选url:目标url,不会检查url是否存在,且不能跨域。如不传该项,即给当前url添加datawindow。history。replaceState(state,title,targetURL);类似于pushState,但是会直接替换掉当前url,而不会在history中留下记录 popstate事件会在点击后退、前进按钮(或调用history。back()、history。forward()、history。go()方法)时触发 注意:用history。pushState()或者history。replaceState()不会触发popstate事件10treeshaking是什么,原理是什么 Treeshaking是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫Deadcodeelimination treeshaking的原理是什么?ES6Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码 扩展:common。js和es6中模块引入的区别? CommonJS是一种模块规范,最初被应用于Nodejs,成为Nodejs的模块规范。运行在浏览器端的JavaScript由于也缺少类似的规范,在ES6出来之前,前端也实现了一套相同的模块规范(例如:AMD),用来对前端模块进行管理。自ES6起,引入了一套新的ES6Module规范,在语言标准的层面上实现了模块功能,而且实现得相当简单,有望成为浏览器和服务器通用的模块解决方案。但目前浏览器对ES6Module兼容还不太好,我们平时在Webpack中使用的export和import,会经过Babel转换为CommonJS规范。在使用上的差别主要有: 1、CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。 2、CommonJS模块是运行时加载,ES6模块是编译时输出接口(静态编译)。 3、CommonJs是单个值导出,ES6Module可以导出多个 4、CommonJs是动态语法可以写在判断里,ES6Module静态语法只能写在顶层 5、CommonJs的this是当前模块,ES6Module的this是undefined11babel是什么,原理了解吗 Babel是一个JavaScript编译器。他把最新版的javascript编译成当下可以执行的版本,简言之,利用babel就可以让我们在当前的项目中随意的使用这些新最新的es6,甚至es7的语法。 Babel的三个主要处理步骤分别是:解析(parse),转换(transform),生成(generate)。解析将代码解析成抽象语法树(AST),每个js引擎(比如Chrome浏览器中的V8引擎)都有自己的AST解析器,而Babel是通过Babylon实现的。在解析过程中有两个阶段:词法分析和语法分析,词法分析阶段把字符串形式的代码转换为令牌(tokens)流,令牌类似于AST中节点;而语法分析阶段则会把一个令牌流转换成AST的形式,同时这个阶段会把令牌中的信息转换成AST的表述结构。转换在这个阶段,Babel接受得到AST并通过babeltraverse对其进行深度优先遍历,在此过程中对节点进行添加、更新及移除操作。这部分也是Babel插件介入工作的部分。生成将经过转换的AST通过babelgenerator再转换成js代码,过程就是深度优先遍历整个AST,然后构建可以表示转换后代码的字符串。 还想深入了解的可以看〔实践系列〕Babel原理12原型链判断 请写出下面的答案Object。prototype。proto;Function。prototype。proto;Object。proto;ObjectinstanceofFunction;FunctioninstanceofObject;Function。prototypeFunction。proto;Object。prototype。proto;nullFunction。prototype。proto;Object。prototypeObject。proto;Function。prototypeObjectinstanceofFunction;trueFunctioninstanceofObject;trueFunction。prototypeFunction。proto;true 这道题目深入考察了原型链相关知识点尤其是Function和Object的之间的关系13RAF和RIC是什么 requestAnimationFrame:告诉浏览器在下次重绘之前执行传入的回调函数(通常是操纵dom,更新动画的函数);由于是每帧执行一次,那结果就是每秒的执行次数与浏览器屏幕刷新次数一样,通常是每秒60次。 requestIdleCallback::会在浏览器空闲时间执行回调,也就是允许开发人员在主事件循环中执行低优先级任务,而不影响一些延迟关键事件。如果有多个回调,会按照先进先出原则执行,但是当传入了timeout,为了避免超时,有可能会打乱这个顺序。 这个题目可以深入去问浏览器每一帧的渲染流程具体可以看看这篇requestIdleCallback和requestAnimationFrame详解困难1Es6的let实现原理 原始es6代码varfuncs〔〕;for(leti0;i10;i){funcs〔i〕function(){console。log(i);};}funcs〔0〕();0 babel编译之后的es5代码(polyfill)varfuncs〔〕;varloopfunctionloop(i){funcs〔i〕function(){console。log(i);};};for(vari0;i10;i){loop(i);}funcs〔0〕();0 其实我们根据babel编译之后的结果可以看得出来let是借助闭包和函数作用域来实现块级作用域的效果的在不同的情况下let的编译结果是不一样的2如何设计实现一个渲染引擎 这道题是字节终面的最后一个题目属于开放性问题没有固定答案我当时觉得题目概念太大了把我整懵了我只是回答了下浏览器渲染原理啥的貌似面试官不太满意哈哈如果叫你设计一个渲染引擎应该从哪些方面着手呢3require具体实现原理是什么 require基本原理 require查找路径 require和module。exports干的事情并不复杂,我们先假设有一个全局对象{},初始情况下是空的,当你require某个文件时,就将这个文件拿出来执行,如果这个文件里面存在module。exports,当运行到这行代码时将module。exports的值加入这个对象,键为对应的文件名,最终这个对象就长这样:{a。js:helloworld,b。js:functionadd(){},c。js:2,d。js:{num:2}} 当你再次require某个文件时,如果这个对象里面有对应的值,就直接返回给你,如果没有就重复前面的步骤,执行目标文件,然后将它的module。exports加入这个全局对象,并返回给调用者。这个全局对象其实就是我们经常听说的缓存。所以require和module。exports并没有什么黑魔法,就只是运行并获取目标文件的值,然后加入缓存,用的时候拿出来用就行。4前端性能定位以及优化指标 前端性能优化已经是老生常谈的一项技术了很多人说起性能优化方案的时候头头是道但是真正的对于性能分析定位和性能指标这块却一知半解所以这道题虽然和性能相关但是考察点在于平常项目如何进行性能定位和分析我们可以从前端性能监控埋点以及window。performance相关的api去回答也可以从性能分析工具Performance和Lighthouse还可以从性能指标LCPFCPFIDCLS等去着手