前言
大家好我是小胡不糊涂,最近工作比较忙一直无心更文,上班前也是拿了好几个offer,希望这些题能给大家前端打工人带来一些帮助,这已经是最近面试题最后更的一篇文章了,续之前的面试题在继续完善,之后希望能一直坚持更新工作中的项目技术坑点、难点,如有说的不太对的地方还望多多包涵指点。想看第一篇文章在这# 浅总结一下前端常见面试题(一),话不多说,咱们直接看题😆😆!
css3、html5部分
1.CSS3新属性
border-radius
实现圆角transform
可以实现盒子水平垂直居中transition
实现过渡效果box-sizing
转换盒子模型 将标准盒子变成怪异盒子RGBA
颜色box-shadow
盒子阴影animation
动画效果
2.实现盒子垂直水平居中
- 父元素开启
dispaly:flex
,justify-content:center,align-items:center - 父元素相对定位
position: relative;
,子元素绝对定位position: absolute;
,top:50%,left:50% 子绝父相
,top:50%,left:50%,transform:translate(-50%,-50%)子绝父相
,top、left、right、bottom为0,margin为auto- 父元素开启
dispaly:flex
,子元素margin:auto salc()
方法,父元素相对定位,子元素绝对定位,left和top值为(50%减去子盒子宽高的一半)table-cell
3.盒子模型
盒子模型分为标准盒子和IE盒子(怪异盒子)使用box-sizing:border-box
可以将标准盒子转换成怪异盒子 标准盒子为content-box
标准盒模型:width宽度等于content的宽度 即内容区域的宽度
怪异盒模型:width等于内容宽度加上padding和border的宽度
4.px、rem、em、vw/vh的区别
-
px是绝对单位,写多少就是多少
-
em是相对单位,相对于当前元素的fontsize 一般用于做首行缩进的效果
-
rem也是相对单位,相对于html根元素的fontsize,用于做移动端的适配效果 平时用lib-flexbile和pxtorem插件进行实现 (原理是document.documentElement.clientWidth获取当前屏幕的宽度,再除以10之后给html的fontsize)
-
100vw就是百分之百的宽度,是新版用来做移动端适配的方案
5.实现小于12px的文字大小
使用scale或者zoom对文字进行缩放
6.CSS的性能优化
- 把多个样式合并成一个样式 并且进行压缩(多行css换成一行)
- 合理使用选择器 层次嵌套最好不好超过三层 减少查找的时间
- 使用css的精灵图(雪碧图) 把多个图片合并成一张图片
- 用iconfont或svg做图标效果 尽量减少图片的使用量
- 将比较小的图片转换成base64的格式 以减少http的请求数量
7.Less和Scss的区别
相同点:
- less和scss都是css的预处理器,可以拥有变量、运算、嵌套等功能,使代码便于阅读和维护
- 都可以通过自带的插件,转成相对应的css文件
- 嵌套规则相同,可以通过选择器的相互嵌套控制某个类名
- 都可以混入参数
不同点:
- 声明和使用变量的方式不同,less使用@,有变量提升,scss使用$符,没有变量提升
- scss可使用条件语句,less不支持 例如if else、for循环 (使用时也要加上$符号)
- scss引入外部文件时,文件名必须以'_'(下划线)开头,
- 编译环境不同,less基于javascript,在客户端处理(可以通过less.js将less代码输出css到浏览器,一般都是在开发环节编译成css文件,再放到项目中),scss基于ruby,在服务端处理
JS、TS部分
1.Generator函数
-
Generator(又叫生成器函数)是es6新增的一种异步编程的解决方案,最大的特点是可以交出函数的执行权,即可以做到控制函数的暂停,做到分批执行函数
-
语法:在function后多个*号符 内部使用yield表达式定义内部状态 Generator函数调用后不会立即执行 而是返回一个遍历器对象 使用Iterator对象中的next()方法可以依次调用Generator函数中的每个内部状态
-
使用next()方法后会返回一个对象 对象中包含value和done属性 value代表yield后的值 done表示布尔值 true/false 表示当前Generator函数是否全部执行完毕 为true时表示Generator函数的所有内部状态已经全部执行完成
-
因为自身携带执行器,所以每次使用都要调用next()方法,但可搭配co库来自动执行,后面也是考虑到用起来不方便,所以se7新增了async/await,自带执行器,所以说
async/await是Generator函数的语法糖
2.this指向
- 全局中的this指向window 严格模式下指向undefined
- 对象中的this指向这个对象
- 构造函数中的this指向实例对象
- 事件绑定中的this指向绑定的DOM元素
- 定时器中的this指向window
- 对象的方法中的定义的函数 this指向window
3.call、apply、bind的区别
都可以实现对this指向的改变
- call、apply会立即调用函数 bind不会立即调用 但会返回一个新的函数
- apply只有两个参数 第一个参数是this的指向 第二个是函数的实参 为数组形式
使用场景:
- call可以用于借用构造函数的继承
- apply可以用于求数组中的最大值
- bind可以将定时器中的this改变成当前作用域中的this
箭头函数没有自己的this
,他的this指向当前作用域中的this,并且不能用于构造函数,会报错
4.事件流
事件流是指页面接收事件的顺序,当多个具有相同事件的元素嵌套在一起时,例如click,在点击其中一个元素时,处理点击的元素会触发事件,层叠在点击范围内的元素都会触发事件
事件流包括事件冒泡
和事件捕获
两种模式:
-
捕获:指父元素先触发事件,子元素后触发,从外到内 true
-
冒泡:指子元素先触发事件,父元素后触发,从内到外 false默认
-
阻止冒泡:stopPropagation vue中为修饰符 stop
-
阻止默认:preventDefault vue中为修饰符prevent
5.forEach、for in 和for of的区别
-
forEach只能遍历数组,且无法使用continue、break、return语句跳出循环
-
for in既可遍历数组也可遍历对象 遍历数组时,代表数组的索引下标,遍历对象时,代表对象的Key值,可以使用break、continue、return跳出循环
-
for of既可遍历数组也可遍历字符串,但是不可以遍历对象, 遍历数组时,代表数组的每一个项的值,遍历对象时,也代表字符串中每一项的值,可以使用break、continue跳出循环。for of可以遍历Set和Map等类数组
break:满足条件时 结束全部循环 continue:满足条件时,结束本次循环,后续循环可继续执行
6.如何理解TS以及TS和JS的区别
TS是一种静态类型检查的语言 ,是 JavaScript 的超集,简单来说就是:JS 有的 TS 都有,可以为JS添加类型注解,在代码编译阶段就可以检查出数据类型的错误
为什么会有TS:JS的类型系统存在‘先天缺陷’弱类型,会使JS中出现大量的类型错误,导致在使用JS的时候,增加了找Bug改Bug的时间,影响开发效率
7.TS的数据类型有哪些
-
boolean(布尔类型)
-
number(数字类型)
-
string(字符串类型)
-
array(数组类型)
-
tuple(元组类型)(赋值的类型、位置、个数需要和定义的类型、位置、个数一致)
-
enum(枚举类型)
枚举是一个被命名的整型常数的集合,用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型 分别为数字枚举、字符串枚举、异构枚举(默认为数字枚举,若第一项设置为字符串枚举,剩下的也需要设置,否则会报错)
-
any(任意类型)
-
null 和 undefined 类型(null表示一个空对象引用,undefined表示一个没有设置值的变量)
-
void 类型
-
never 类型(never 类型一般用来指定那些总是会抛出异常、无限循环)
-
object 对象类型
8.interface 和type的区别
- interface(接口)和 type(类型别名)的对比:
相同点:
都可以给对象指定类型
不同点:
-
interface,只能为对象指定类型
-
type,不仅可以为对象指定类型,实际上可以为任意类型指定类型别名
namespace命名空间也是一种声明类型的方式
计算机网络、工程化部分
1.跨域
由于浏览器的同源策略 即协议、域名、端口都必须一致,否则就会引起跨域,发送ajax请求的时候就会报错,这个现象是浏览器的安全策略导致的
解决跨域的方案:
- Jsonp 利用script标签可以实现跨域访问 实现思路:服务器将返回给前端的数据放在一个函数中,前端也要提前准备好一个全局函数,此全局函数的名称必须和后台数据的函数名一致(前端通过callback将名称传递给后台,后台取到传递过来的值作为函数的名称),然后通过script标签拿取后台数据,但是jsonp只能支持get请求,因此现在很少有公司使用
- cors cors本来是会跨域的 但是后端可以增加http头,声明允许访问来源,服务器就会允许这个域名直接进行访问 类似于白名单
- .proxy 反向代理 就是使用一个中间服务器,将接收到的客户端发送的请求代理转发给目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地,由于代理服务器是在本地创建,因此只适用于开发阶段。服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器的安全策略限制的
具体实现方式:
- devServer中的proxy是关于代理的配置,proxy对象中的属性是代理的规则匹配,表示被代理的请求路径前缀,一般为了辨别都为设置为 /api, 匹配规则对应如下:
- target:表示的是代理到的目标地址
- pathRewrite 路径重写,默认情况下用于匹配的/api也会被写入到url中,此配置可以删除
- changeOrigin:表示 是否更新 代理后请求的headers中的host地址
- 生产环境中,后台JAVA可以通过nginx进行设置
2.地址栏敲下回车后发生了什么
- URL解析 首先判断输入的是合法的URL 还是待搜索的关键词,并根据输入的内容进行对应操作
- DNS 查询 将域名解析为 IP 地址
- TCP连接
- 发送http请求 tcp连接之后,就可以在这基础上进行通信,浏览器发送 http 请求到目标服务器
- 响应请求 服务器接收到浏览器的请求之后,就会进行逻辑操作,处理完成之后返回响应消息
- 页面渲染 浏览器接收到服务器响应的资源后,对资源进行解析
3.为什么TCP需要三次握手和四次挥手
三次握手: 指建立TCP连接时,需要客户端和服务端总共发送三个包,主要作用是为了确认双方的接收能力和发送能力是否正常,并指定自己的初始化序列号为后面的可靠性传送做准备
-
- 第一次: 客户端给服务端发送SYN报文,并指明客户端的初始化序列号(ISN),此时服务器可以确认客户端的发送能力、服务端的接收能力正常
- 第二次: 服务器收到SYN报文后,会以自己的SYN报文作为应答,并将客户端的ISN+1作为ACK的值。此时客户端可以确认服务端的接收能力、发送能力是正常的
- 第三次: 客户端收到SYN报文后,会发送一个ACK报文,值为服务器的ISN+1,双方建立起连接。此时服务端可以确认客户端的接收能力、发送能力正常,服务端自己的接收能力、发送能力也正常
为什么不能是两次握手: 因为在服务端接收到客户端发送的报文并返回后,服务端并不能确认客户端的接收能力是否正常,所以需要再次握手确认客户端的接收能力是否正常,是否可建立可靠性的传送
四次挥手:指TCP终止连接时,需要经过四次挥手
-
- 第一次:客户端发送一个FIN报文,报文中指定一个序列号,此时客户端停止发送数据,等待服务端确认
- 第二次: 服务端收到FIN后,发送ACK报文表明服务端已经收到服务端断开连接的FIN报文, 此时并不会立即关闭连接 客户端不再发送请求,但是服务端若发送数据,客户端依然要接收
- 第三次: 服务端的所有报文发送完毕后,会向客户端发送FIN报文,服务端关闭客户端的连接
- 第四次: 客户端收到FIN报文后,也会发送ACK报文作为应答。关闭完成
为什么需要四次挥手: 因为在服务端收到客户端发送的断开连接请求后,并不会立即关闭,需要等待服务器将全部的报文发送完毕后,再发送FIN通知客户端关闭,客户端确认后,完成关闭。
4.HTTP和HTTPS的区别
- HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理,相对更安全
- HTTP 和 HTTPS 使用连接方式不同,默认端口也不一样,HTTP是80,HTTPS是443
- HTTPS 由于需要设计加密以及多次握手,性能方面不如 HTTP
- HTTPS需要SSL,SSL 证书需要钱,功能越强大的证书费用越高
5.HTTP中常见的状态码有哪些
- 200 成功 服务器已经成功处理了该请求,通常表示服务器提供了请求的数据
- 201 已创建 请求成功并且服务器创建了新的资源
- 3xx 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向
- 400 错误请求 服务器不理解请求的语法
- 401 未授权 身份验证(token)失败
- 403 服务器拒绝请求
- 404 未找到 服务器找不到请求的网页
- 5xx 表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生
6.GET和POST的区别
- get请求会被浏览器主动cache(缓存),而post不会,除非手动设置
- get参数通过 url传递,post在request body中
- get参数直接暴露在url中,因此很不安全,不能用来传递敏感信息
- get请求传递参数有长度限制,而post没有
- get请求的参数会被保存在浏览器的历史记录里面 post中的参数不会被保留
7.Ajax和Axios的区别
ajax是基于原生的XHR
,实现了局部数据的刷新,axios是通过promise
来实现对ajax技术的封装。
8.hash和history的区别
1.hash模式在地址栏中会有#号键,history没有#号键
2.hash可以兼容到ie8,history可以兼容到ie10
3.hash变化不会导致浏览器向服务器发送请求,并且hash改变会触发hashchange事件
,但是该事件只能改变#号后面的url片段,不会发送http请求,对后端没有影响,也不会重新加载页面
原理:
1.hash通过监听浏览器的onhashchange()
事件变化,查找对应的路由规则
2.history是利用H5新增的的push State()
和replace State()
和onpopstate
监听路由变化
history模式中url要和后端一致,因此使用history也需要后端的配合
,否则会报错
9.什么是模块化
将js分成多个模块进行编写,就是不要把所有的js代码放在一个文件中,因为这样难以维护 变量名容易冲突,所以代码尽量一个功能一个js文件,一个文件最好要有一个自己的作用域,这样不会和别的文件冲突,文件最终要组合在一起,所以还有一个导入,这个就是模块化
-
command.js: require导入 module.export导出
-
es6规范:export default、export导出 import 导入
10.webpack
webpack是一个前端的静态资源打包工具,仅分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中
核心概念:
1.mode 打包模式development 开发模式 production 生产模式
2.entry 打包入口文件 是模块构建的起点
3.output 打包的出口文件 是模块构建的终点 一般为bundle.js
4.loaders 配置处理各模块的loader,用于对模块的‘源代码’进行转换,在import或‘加载’模块时预处理文件
- webpack只能解析js文件或json文件,对于其他的文件类型则无法解析,所以需要配置对应的loader进行文件内容的解析,并且webpack只能解析部分es6,还需要babel-loader用来做降级处理 常见loader:css-loader,less-loader,sass-loader,style-loader
- 写法:loader的配置写在module.rules属性中,rules是一个数组,可以配置多个loader 配置的loader为一个对象,其中test为匹配的规则(即正则的文件后缀名),use配置该文件对应的loader
5.plugins 各插件对象,在 webpack 的事件流中执行对应的方法 其他都是插件做的事情 常见插件:
- html-webpack-plugin:主要作用就是在webpack打包结束后自动生成一个html文件,并把打包生成的js模块引入到该html中。
- extract-text-piugin:该插件的主要是为了抽离css样式,防止将样式打包在js中引起页面样式加载错乱的现象,如果不使用该插件,打包之后会将css代码以字符串的形式混入打包后的index.bundle.js文件中,并且index.bundle.js文件会比平时大一些
11.loader和plugin的区别
- loader是文件加载器,可以加载资源文件,并对这些文件进行某种处理,例如编译、压缩等,最终一起打包到指定文件中
- plugin可以使webpack的功能更加灵活,例如打包优化、资源管理等,目的是为了解决loader无法实现的其他功能
12.webpack优化前端性能
-
压缩代码
- 通过删除多余的代码、注释、简化代码的写法等方式来压缩JS和CSS文件,减少体积
-
采用CDN加速
- 在构建过程中,将引入的静态资源路径修改为CDN上对应的路径
-
Tree Shaking
- 将代码中不会用到的片段删除掉
-
Code Splitting
- 将代码按照路由或者组件进行分块,做到按需加载,同时充分利用浏览器缓存
-
第三方库
- 使用SplitChunksPlugin插件进行公共模块的抽取,利用浏览器缓存可以长期缓存这些无需频繁变动的公共代码
VUE部分
1.Vue2和Vue3的区别
- vue2的双向数据绑定是通过Object.definePropert()对数据进行劫持来实现,由于需要对每个属性进行遍历递归劫持,当层次嵌套较深时性能较差,并且动态添加的数据无法实现响应式效果,数组也无法监听,Vue3通过Proxy可以对整个对象进行代理,提高性能的同时,也可以实现对数组的监听
- Vue3的源码用TS重写了,完全支持TS,因此可以在Vue3的项目中引入TS写法,使语法更加严格,让报错提前,方便多人的协作
- Vue2使用Options选项Api,Vue3使用composition的Api,让代码更好维护和复用
- Vue3中可以有多个根元素
- Vue3的API是按需引入的,可以实现Tree Shanking的优化
- Vue3的代码都是写在setup中,因此没有this
- Vue3中没有EventBus,可使用mitt第三方库替代
Vue3的设计目标:更小、更快、更友好
2.Vue3的性能优化
- diff 算法优化 Vue3在Vue2的diff算法的基础上添加了静态标记,将可变化的地方添加一个flag标记,下次变化的时候直接找该地方进行比较,被标记为静态节点的元素不会进行比较,提升性能
- 静态提示 Vue3中对不参与更新的元素会做静态提升,只会创建一次,在渲染时直接复用,免去重复的创建节点
- 事件监听缓存 默认情况下绑定事件行为会被视为动态绑定,所以每次都会去追踪它的变化,开启缓存后,每次重新渲染时如果事件处理器没有变,就会使用缓存中的事件处理而不会重新获取事件处理器。这个节点就可以被看作是一个静态的节点。这种优化更大的作用在于当其作用于组件时,之前每次重新渲染都会导致组件的重新渲染,在通过handler缓存之后,不会导致组件的重新渲染了
- SSR优化
- Vue3的源码整体体积变小(Tree shanking),移出了一些不常用的API,将没有用到的模块摇掉,使打包的整体体积变小
3.Vue3的生命周期钩子函数
4.Vue中组件和插件有什么区别
组件: 组件就是将某种功能,把图形、非图形各种逻辑抽象成组件的形式进行开发,在Vue中每一个.vue文件都可以视为一个组件
组件的优势:
- 降低系统的耦合性,提高可维护性,多次复用,调试方便
插件: 插件通常为Vue添加全局功能,功能的范围没有严格限制,例如:
- 添加全局属性或者方法
- 添加全局资源:指令/过滤器等
- 插件的实现需要使用install方法,方法的第一个参数时Vue构造器,第二个参数时一个可选的选项对象
组件通过component注册,用来构建APP的业务模块,目标时App.vue
插件通过use注册,用来增强技术栈的功能模块,目标是Vue本身
简单来说,插件就是对Vue功能的增强或者补充
5.自定义指令
对DOM元素进行操作
- bind 只会调用一次,指定在第一次绑定到元素时调用,可以进行一次性的初始化设置
- inserted 被绑定元素插入到父节点时调用(仅保证父节点存在,但不一定已被插入到文档中)
- update 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值
- componentUpdate 被绑定元素所在模板完成一次更新周期时调用
- unbind 只调用一次,指令与元素解绑时调用
常用的自定义指令:
-
v-longpress 实现长按,用户需要按下并按住按钮几秒钟从而触发相应的事件
- 创建定时器,2秒后执行函数,用户按下按钮触发mousedown事件,启动定时器。松开按钮时调用mouseout事件,如果mouseup事件在2秒内被触发,清除定时器,当做普通点击事件处理,否则判定为长按,执行对应函数
-
v-debounce 自定义指令防抖 避免某些按钮被多次点击,多次重复请求后端接口造成数据混乱
- 定义一个延时执行的方法,如果在延迟时间内再次调用该方法,则重新计算执行时间,时间结束再执行binding.value()
-
v-LazyLoad 页面图片过多时会影响也页面加载速度,懒加载可以只让浏览器加载可见区域的图片
- 判断当前图片是否进入可视区,进入可视区后再设置图片的src属性,否则只显示默认图片
- 使用VueUse中的useIntersectionObserver可以判断图片是否到了可视区域
-
v-permission 实现按钮级权限设置
- 根据用户的权限点列表,判断该按钮对应的权限点是否在列表内即可
Vue3中的自定义指令钩子函数:
- created、beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted
6.vue中常见修饰符
表单:
- lazy 离开标签时才会触发,相当于change事件
- trim 去除两边输入的空格
- number 将input变成数值型 只能输入数字
事件修饰符:
- stop 阻止冒泡
- prevent 阻止默认事件
- once 只会执行一次
- native 给组件绑定事件时
- self 只有当元素自身触发时才会执行对应函数
鼠标和键盘修饰符:
- left、right、middle分别代表鼠标左、右、中键
- keyup、keydown的修饰符,enter、esc等
7.vue-Router原理
通过Vue.use注册插件
,在插件的install方法中获取用户配置的router对象,当浏览器地址发生改变的时候,根据router对象匹配对应的路由,获取组件并将组件渲染到视图上,并通过hash和history两种模式实现前端路由
补充
还有被问到的问题我自己也只能说个大概,感兴趣的同学可以在去多了解一下或者评论交流,我列出来。
- 路由分模块加载如何实现
- keep-alive如何清除三层以内的数据
- app兼容iOS数据过多卡顿,弹性滚动,如何解决
- 各类循环为什么for循环最快
- 迭代器和生成器举例说明
- 大文件分片上传如何实现
- ts中的类型工具keyof和typeof区别
- ts中any和unkown和never区别
- vue2和vue3中v-model有什么区别
- vite基本原理实现
写在最后
终于肝完啦,码字不易,如有不太正确的地方请见谅,多多指教。面试题总算告一段落了,也是对自己文笔和知识复习的一种锻炼😎😎,需要看往期第一篇的链接在这 # 浅总结一下前端常见面试题(一),下次更新再见🧐🧐