前端面试题

424 阅读13分钟

记录一下遇到过的前端面试题,顺便整理一份自己的答案。

CSS题

一、# Css、Less 和 Sass(SCSS)的区别详解

答:
css是静态定义样式,不支持变量、混合、嵌套等编程语言特性。为了解决这些问题,就诞生出less跟sass这些预处理器。
Less 特性是变量、混合、嵌套、函数和运算,比较合适中小型项目。
Sass 特性还多了继承导出导入,合适一下复杂项目。

JS题

一、0.1+0.2为什么不等于0.3

答:
JS的数值是双精度浮点数格式的,这时候0.1跟0.2实际上都是无限循环小数,没法用二进制精确表示,这时候再去运算的话,就会出现误差,所以不等于0.3了。
如果想避免出现误差,可以先把0.1、 0.2乘10再去运算,后面再除10。

二、说几条前端优化

答:
1、图片类的,可以使用sprites图(雪碧图),懒加载图片,不同屏幕尺寸使用不同分辨率的图片,使用svg或者说iconfont代替一些图片,尽量使用png代替jpg。
2、减少重绘跟回流,具体可以使用vue、react这些mvvm框架。
3、使用cdn分发网络,这样能加快用户的获取资源速度。(题外话,这个前端业务是不用考虑的了,而且小公司也没这个需求)
4、合理利用缓存,减少服务器请求次数。
5、尽量减少服务器请求,可以把相同类型的合并在一起。
6、减少cookies的数量,cookies会每次请求都发去服务器。
7、减少同类型的事件绑定,可以使用事件委托而代替。
8、一些要展示大量数据的列表之类可以使用虚拟列表来减少dom元素。
9、尽量使用CSS3动画代替一些JS动画或者git图。
10、使用防抖节流来控制高频事件请求。
分享一个比较具体的,juejin.cn/post/746865… ,这个的说明很详细

三、ES6新增哪些内容

答:
现在已经到es14,但是es6跟之前的相比提升是巨大的
1、变量声明方式新增了let、count,解决了var的各种问题,例如变量提升、没有块级作用域等
2、箭头函数()=>{},箭头函数this指向要注意,它是从上下文寻找的,没有自身的this
3、函数入参默认值,有默认值的只能放在最后,否则会报错
4、解构赋值
5、拓展运算符
6、模板字符串
7、promise对象,解决了callback回调地狱了,但是一样会有then地狱,所以后续新增await和async,可以用同步的写法去编写异步函数。
8、新增了两个数据结构,分别是Set跟Map,Set是类似数组的数据结构,它里面的数据具有唯一性,可以快速给数组去重了;然后Map就是跟对象差不多的,是一种键值对数据结构,在其他语言里面是叫做字典,它的key可以是任何东西,包括方法等,相对于对象,它还自带一些像size的方法,查询多少个属性等便捷方法。
9、模块化,export和import,为前端开发提供了模块化开发的可能性。
10、新增了一个新的基础数据类型Symbol,它是代表独一无二的值;后面的版本还加了个bigint,突破了number类型的最大最小值。
11、Class,类跟继承,后面版本陆续扩展了静态、私有等功能。顺带提一嘴,里面的方法最好不要用箭头函数,this的指向会指向外面,而不是指向class实例。
12、给各类型数据,例如字符串、数组、对象新增了很多方法,就不一一细举了

四、Cookie、sessionStorage、localStorage的区别

答:
他们都是同源下存放的,而且是存放在用户浏览器上面。
其中sessionStorage、localStorage可以统称为webStorage。
1、他们的存放空间大小不一样,cookies比较少,只有4kb;webStorage则可以去到5mb或者更大。
2、cookies会参与所有与服务器的请求,会消耗带宽,而webStorage。
3、有效期不一样,一般默认情况cookies是只要关闭浏览器就没了,当然,我们也可以控制它的生命周期。localStorage的话,也是一直保存的;sessionStorage只要关闭当前页面就没了。

五、说一下防抖和节流

答:
这两个是为了解决高频触发事件
防抖是每次触发都把计时器清理掉,不执行里面的方法,再重新计时,计时结束后执行。可以解决输入框输入变化过快之类的。
节流也可以用计算器,也可以记录lastTime,跟curtime比,每次触发判断一下是否在delay范围外就执行,第一次就开始执行,可以用来解决滑动触发事件之类高频事件。

六、mvc与mvvm

答:
是两种开发模式,mvc是比较传统的开发模式,mvvm是前端的新开发模式,两种模式的前两个字母都是代表同一样东西,m是model,也就是数据模型,v是view,也就是视图,c是Ctrl,控制器,vm是view-model。
mvc的三层是互相影响的,而且三者都需要开发去开发维护,简单来说就是用户在view层输入指令,通过ctrl层处理逻辑,再发送到model层,最后更新view层。这样会导致view层会依赖model层,从而导致一些业务逻辑不好复用
mvvm的话是将“数据模型数据双向绑定”的思想作为核心,view层跟model层不再粘合在一起,没有直接关系,是通过view-model层实现双向绑定,一般来说,我们前端可以使用vue或者react等框架提供view-model层,业务开发只需要专注于view层跟model层开发就好了。

七、浏览器打开一个网页的全流程(里面包括回流跟重绘)

答:
1、用户输入url地址,通过dns解析ip地址,找到服务器, 顺带提一句一个域名可以对应多个IP, 用户在访问时可以被分配到最近的服务器, 这样就可以做负载均衡和容灾(虽然这不是前端开发的工作了)。
2、找到对应服务器后就开始三次握手tcp连接了。
3、tcp成功后,客户端开始接收数据了,开始解析html构建dom树,解析css构建Rule树,然后合在一起构建成渲染树,然后计算布局,计算样式,如果后面有位置样式变化就会重复计算布局,计算样式操作,这个就是回流跟重绘,这部分是会相对消耗性能的。vue等框架会构建一个虚拟dom,通过diff算法去尽量减少回流跟重绘。直到没有回流跟重绘就渲染页面完成了。
4、到最后就是关闭页面了,关闭页面会跟服务器来个4次挥手的通知服务器关闭了,不用再发消息过来。

八、讲一下闭包

答:
当通过调用外部函数返回的内部函数后,即使外部函数已经执行结束了,但是被内部函数引用的外部函数的变量依然会保存在内存中,我们把引用了其他函数作用域变量的函数和这些被引用变量的集合,称为闭包(Closure)
然后闭包有3种主要用途,
1、封装私有变量:闭包可以用于封装私有变量,以防止其被外部访问和修改。封装私有变量可以一定程度上防止全局变量污染,使用闭包封装私有变量可以将这些变量限制在函数内部或模块内部,从而减少了全局变量的数量,降低了全局变量被误用或意外修改的风险。
2、做缓存:函数一旦被执行完毕,其内存就会被销毁,而闭包的存在,就可以保有内部环境的作用域。
3、模块化编程(实现共有变量):闭包还可以用于实现模块化编程。模块化编程是一种将程序拆分成小的、独立的、可重用的模块的编程风格。闭包可以用于封装模块的私有变量和方法,以便防止其被外部访问和修改。

闭包也存在着一个潜在的问题,由于闭包会引用外部函数的变量,但是这些变量在外部函数执行完毕后没有被释放,那么这些变量会一直存在于内存中,总的内存大小不变,但是可用内存空间变小了。 一旦形成闭包,只有在页面关闭后,闭包占用的内存才会被回收,这就造成了所谓的内存泄漏。
因此我们在使用闭包时需要特别注意内存泄漏的问题,可以用以下两种方法解决内存泄露问题:
1.及时释放闭包:手动调用闭包函数,并将其返回值赋值为null,这样可以让闭包中的变量及时被垃圾回收器回收。
2.使用立即执行函数:在创建闭包时,将需要保留的变量传递给一个立即执行函数,并将这些变量作为参数传递给闭包函数,这样可以保留所需的变量,而不会导致其他变量的内存泄漏。

九、async/await

async函数返回的是promise对象

vue题

一、vue2跟vue3的区别

答:
vue2、vue3的区别有好几点,由底层开始说吧

1、diff算法不一样了,vue2的diff算法是会把每个节点都遍历一次去查询区别再去生成patch对象更新dom.
vue3的diff算法是在节点生成的时候动态添加patchflag标记,只对比带有patchflag标记节点。

2、响应式原理不同双向绑定不一样了,vue2是利用Obejct.defineProperty,vue3是利用es6新增的proxy。Obejct.defineProperty是支持ES5的,所以在旧版ie上也能使用,相对而言,vue3就已经是完全不支持旧版ie了。 但是proxy带来的巨大的性能提升。
Obejct.defineProperty是需要在初始化的时候遍历对象去劫持数据来监听的,这样导致无法监听数据的新增跟删除,还有数组的监听,vue2是重写了数组的方法才能实现监听,但是数组的长度跟索引改变这两个是没有对应的方法,vue2则是另外写了两个方法去修改数组来去实现响应式。
相反而言,proxy则没有这些问题,它本身自带监听的功能,不过proxy也有做不到的事情,那就是无法拦截内部的方法,vue3是再改了一点,会判断每个属性是否方法,然后再去新增proxy对象。

上述的两点是比较底层的变动,对于平常业务来说,值得注意的就是兼容性跟数组的监听的问题,接下来再说一下业务上的改动。

1、vue3出现了setup语法糖,这样就可以让vue3用 Composition Api,所有方法都要动态引入,vue2或者不用语法糖的时候还是Options Api,这样可以让我们写逻辑的时候更好的分类了,不用左切右切找方法了。

2、template里面可以多节点了,vue2是只能单节点。

3、生命周期变化了,命名大部分都是多了个on,销毁的命名更加规范了,语法糖setup是默认等同于created,钩子函数也是要动态引入。

4、数据传输方面,vue3多了个provide,inject,其他的跟vue2差不多。
全局事件的eventbus不一样了,vue2是通过另外一个vue实例来创建eventbus,vue3是通过mitt工具库去创建的。
全局状态管理的工具库也不一样了,vue2是用vuex,vue3是使用pinia,pinia更加简洁,功能更加强大,pinia支持ts,而且支持多个store,删除了mutations方法。

5、更加支持ts了。

6、v-if跟v-for的优先级不一样了,vue2是v-for优先级更高,同时写在一起的话,会造成性能消耗;vue3的话,v-if优先度更高,但是v-if判断条件含有循环的数据,会直接报错
一般建议外层套多层,或者v-for的数据提前判断,实在不行还能套多层template,在template上面v-for,里面v-if,这样就不会增加元素结构了。

二、vue的数据传输/组件内的通讯

答:
1、父子传参可以用props 和 emits,v-model其实就是props跟emits的封装
2、通过路由(地址栏)传参
3、全局事件eventBus
4、全局状态vuex或者pinia
5、深层次组件传参可以使用依赖注入provide,inject;vue2可以使用attrsattrs跟listeners,这两个是没有使用props或者监听事件的时候,会把数据跟事件分别存放实例这两个属性上面,通过这两个属性我们可以将此组件实例当做一个中间件把数据传出去。
6、访问vue组件实例,方法有很多,parentparent、children、ref等,然后再调用实例方法传值。
7、插槽slot也能传递参数
8、window环境变量啊,cookies,Storage这些缓存数据也可以当作数据传过去

三、vue的diff算法

答:
vue2的会把新旧虚拟dom每个节点都遍历一次去查询区别再去生成patch对象更新dom
具体就是先同级比较,再比较子节点,先判断一方有子节点一方没有子节点的情况,比较都有子节点的情况,递归比较子节点重复下去。
vue3的diff算法是在节点生成的时候动态添加patchflag标记,只对比带有patchflag标记节点。

四、data/props复杂类型为什么要写个function return出去

答:
如果直接引用对象的话,多个实例引用的对象指针都是指向同一个地址了,这样就会造成互相影响,这显然不是我们想要的效果。
所以我们需要用个function return个对象出去,这样才能保证每个实例的数据是新的,唯一的。

五、说一下 watch 与 computed 的区别是什么?

答:
如果一个值依赖多个属性(多对一),用computed肯定是更加方便的。 如果一个值变化后会引起一系列操作,或者一个值变化会引起一系列值的变化(一对多),用watch更加方便一些。 watch 支持异步代码而computed 不支持。
computed支持缓存,只有依赖数据发生改变,才会重新进行计算
watch不支持缓存,数据变化,直接会触发相应的操作;