面试其实是对过往工作学习的一个总结,所以面试题的回答也基本就是每个人当下真实水平的体现。本文是参考了很多文章对当下一些热门基础知识面试题的精简总结,当然也是我目前水平能给出的答案,如果你觉得小儿科了那说明你水平远超于我可以划走了。
vue相关
vue的虚拟dom
虚拟dom就是利用js对象来描述真实dom的结构和数据,并利用diff算法对比新旧节点改动计算出最小的变更,来减少dom操作,达到性能优化的目的。
template&jsx
vue分为运行时版本和带编译的完整版本。运行时版本在编译期通过 vue-loader 和 vue-template-compiler 插件将 template 模板经过 template => ast => render函数 => virtual dom => patch => dom
渲染页面;完整版本可以在初始化 new Vue()
时注入 template 选项,运行时将 template
经过 转ast => ast优化 => render funciton code
生成VNode,再渲染到页面。template 模板可以在编译时做静态标记和分析,提升性能。
jsx是一种可以在js中编写html的语法糖,jsx会被 babel 编译为render函数。jsx 相比 template 更灵活,template(vue的template)相比 jsx 更容易上手和接近 html 语法。
vue3的变化
详细答案查看这篇文章
响应式实现方式:
v2中响应式系统的核心是es5的 Object.defineProperty
,劫持整个对象,然后进行深度遍历所有属性,给每个属性添加 getter
和 setter
,实现响应式。
v3中使用es6的 Proxy
和 Reflect
重写响应式系统,可以动态的监听属性的增删改和数组的索引、length的变化。而v2动态增加属性需要使用 $set
函数。数组变化只能使用push\splice等函数。
编译优化
- V2 通过标记静态节点,优化 diff 的过程;V3 通过标记和提升所有的静态根节点,diff 的时候只需要对比动态节点内容。
- 新引入
Fragments
(标识片段)特性,组件可以多个根节点。 PatchFlag
,在进行虚拟 DOM 的 diff 过程中,通过检查节点的PatchFlag
,Vue 可以更精确地确定哪些部分需要更新,从而减少不必要的操作,提高性能。- cacheHandler 缓存事件处理函数减少了不必要的更新操作
diff算法优化
vue3 diff 算法跳过了静态节点对比。
vue2的diff算法采用了双端diff算法:
- 同时使用四个指针分别放到old子节点和new子节点的头和尾。
- 对比头头,尾尾,头尾,尾头,如果其一满足sameVnode,则进行真实dom复用且不需要移动真实dom,指针向内移动。否则,通过keyToOldIdxMap尝试快速找到old子节点中key相同sameVnode,如果找到,则复用真实dom,并移动到当前位置,然后将old子节点[i]设置为undefined,后续查找忽略此节点。如果没有找到则创建新的真实dom并插入。
- 最后满足old子节点的头尾交叉,或new子节点的头尾交叉。说明对比完了。此时如果old交叉,new未交叉,说明new子节点剩下的都是要新创建并插入。反之,old未交叉,new交叉,说明old子节点剩下的是多余的,需要从dom中移除。
vue3采用了快速diff算法:
- 首先进行预处理,将前置和后置可复用的节点找出,且是不需要移动的。
- 判断是否存在剩余节点。如果old子节点存在剩余节点,new子节点不存在,比对完成,移除多余的old子节点对应的真实dom。如果old子节点不存在剩余节点,new子节点存在,比对完成,新建多余的new子节点对应的真实dom并插入。如果old和new子节点都存在剩余子节点,则将剩余部分进行比对复用。
- 剩余部分比对非vue2的比对方式,用到了最长递增子序列,复用与最长递增子序列内的节点对应的old真实dom时,无需移动。进一步减少移动次数,提高了性能。
组合式api
v3提供了组合式api,相比选项式api,能够更好的代码组织、更好的逻辑复用、更好的tree-shaking、不用写代码时候反复横跳。
生命周期钩子变化
最明显的变化是之前的beforeCreate和created对应组合式api的setup。
内置ts支持
v2+ts对ts的支持不是很好。v3全面支持ts,体验效果非常好。
组件支持多个根节点
语法改动
vue2+ts中,可以使用 PropSync+.sync
语法糖实现双向数据绑定和组件传参,vue3中通过 v-model:value=""
实现; vue2+ts中,可以直接通过在函数名前加 get
获得设置计算属性,vue3中必须使用 computed
函数来实现;
vue 性能优化
组件懒加载、异步组件 defineAsyncComponent()、keepalive、合适选择v-if/v-show、长列表用虚拟滚动组件
vue2 单个根组件和 vue3 多个根组件
Vue使用虚拟DOM来提高渲染性能,它会将组件的模板转化为虚拟DOM树。一个组件内部的多个根元素会导致虚拟DOM的结构变得复杂,不利于高效的比较和更新。从效率上讲,如果逻辑抽象树有多个根,那么就会产生多个入口,这对于遍历、查找、比较都不方便。
vue3引入了基于标记片段的编译机制,这种改进使得Vue 3的编译器能够更好地处理多个根级标签,并在实际渲染时保持它们的作用域和响应式数据。 标记片段是一种特殊的数据结构,可以容纳多个子节点,并没有实际的DOM元素。
网络协议
完整的网页加载过程
- DNS域名解析
- 发送HTTP请求(五层网络协议、三次四次、网络缓存)
- 服务端响应(状态码、跨域)
- 浏览器解析(dom、cssom、rendertree、js/css阻塞)
域名解析过程是怎样的
查看浏览器缓存是否有域名对应的ip地址 -> 系统缓存:本地 Hosts 文件 DNS 缓存 -> 路由器缓存 -> 网络供应商 DNS 缓存服务器 -> 根服务器查询对应的域服务器 -> 顶级域名服务器查询对应的解析服务器 -> 解析服务器解析出域名的ip -> 保存结果至缓存
http1.0、1.1和2.0区别
1.0 每次请求都打开一个新的TCP链接,收到响应之后立即断开连接。
1.1 默认开启持久连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。增加了缓存控制标识Cache-Control,tag,If-Unmodified-Since, If-Match, If-None-Match。
2.0 支持多路复用,比 1.1 可发起请求多,且无需排队,避免了线头阻塞;支持头部压缩,建立连接后双方各自缓存头部信息,减小后续请求传输内容。新的二进制格式,使用二进制协议层把所有消息封装成二进制进行传输,比1.1的文本协议更强大。
http和https区别
http是明文协议,相当于在网络上裸奔。https 在 TCP 和 HTTP 之间增加了 SSL 层,所有传输的内容都经过加密的。
https 需要付费向机构申请证书,建立链接需要通过身份认证和传输加密,更安全。
端口号不同;
TCP和UDP区别
UDP是无连接的,想发数据就发,不保证送达;高效,报文头部小,没有三次握手四次挥手;支持一对一一对多多对多的传输方式,即单播,多播,广播的功能。
TCP是面向连接的,需要三次握手四次挥手建立可靠的传输通道,有拥塞控制能力。
状态码
HTTP状态码表示客户端HTTP请求的返回结果、标识服务器处理是否正常、表明请求出现的错误等。
2XX | 成功(这系列表明请求被正常处理了) |
---|---|
200 | OK,表示从客户端发来的请求在服务器端被正确处理 |
204 | No content,表示请求成功,但响应报文不含实体的主体部分 |
206 | Partial Content,进行范围请求成功 |
3XX | 重定向(表明浏览器要执行特殊处理) |
---|---|
301 | moved permanently,永久性重定向,表示资源已被分配了新的 URL |
302 | found,临时性重定向,表示资源临时被分配了新的 URL |
303 | see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源(对于301/302/303响应,几乎所有浏览器都会删除报文主体并自动用GET重新请求) |
304 | not modified,协商缓存,与重定向无关 |
307 | temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 |
4XX | 客户端错误 |
---|---|
400 | bad request,请求报文存在语法错误 |
401 | unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 |
403 | forbidden,表示对请求资源的访问被服务器拒绝,可在实体主体部分返回原因描述 |
404 | not found,表示在服务器上没有找到请求的资源 |
5XX | 服务器错误 |
---|---|
500 | internal sever error,表示服务器端在执行请求时发生了错误 |
501 | Not Implemented,表示服务器不支持当前请求所需要的某个功能 |
503 | service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 |
三次握手
SYN表示建立连接报文,FIN表示关闭连接报文,ACK表示响应报文。
为了建立可靠的数据传输通道,客户端和服务端都需要确定双方的发送和接收能力。
第一次握手,客户端发送SYN请求连接,服务端确认客户端的发送能力正常(和自己的接收能力);第二次握手,服务端发送SYN+ACK确认链接,客户端确认服务端的发送和接收能力正常(同时确定了自己的发送接收能力);第三次握手,客户端发送ACK确认链接,服务端确认客户端的接受能力正常(和自己的发送能力);
四次挥手
SYN表示建立连接报文,FIN表示关闭连接报文,ACK表示响应报文。
为了保证数据完整的被接收完再关闭连接,双方需要都达到关闭连接的条件才能断开。
第一次挥手:客户端发起关闭连接的请求给服务端(FIN);
第二次挥手:服务端收到关闭请求的时候可能这个时候数据还没发送完,所以服务端会先回复一个确认报文(ACK),表示自己知道客户端想要关闭连接了,但是因为数据可能还没传输完,所以还需要等待;
第三次挥手:当数据传输完了,服务端会主动发送一个 FIN 报文,告诉客户端,表示数据已经发送完了,服务端这边准备关闭连接了。
第四次挥手:当客户端收到服务端的 FIN 报文过后,会回复一个 ACK 报文,告诉服务端自己知道了,再等待一下没收到服务端报文就关闭连接。
疑问:
- 为什么握手要三次,挥手却要四次呢?那是因为握手的时候并没有数据传输,所以服务端的 SYN 和 ACK 报文可以一起发送,但是挥手的时候有数据在传输,所以 ACK 和 FIN 报文不能同时发送,需要分两步,所以会比握手多一步。
- 为什么客户端在第四次挥手后还会等待 2MSL?等待 2MSL 是因为保证服务端接收到了 ACK 报文,因为网络是复杂了,很有可能 ACK 报文丢失了,如果服务端没接收到 ACK 报文的话,会重新发送 FIN 报文,只有当客户端等待了 2MSL 都没有收到重发的 FIN 报文时就表示服务端是正常收到了 ACK 报文,那么这个时候客户端就可以关闭了。
web安全防护
XSS 攻击全称跨站脚本攻击,是利用html可以执行<script>alert(1)</script>
的特性,想尽办法将脚本注入页面中的攻击手段。防护:对输入框、文本域等标签的输入内容进行过滤。
CSRF中文名为跨站请求伪造,利用伪装页面等让用户做自己不知道的操作。防护:一般是由服务端解决,接口设置禁止跨域。
打包工具
webpack&vite
webpack 是个模块打包工具,一切皆模块。通过静态分析各种模块文件之间的依赖关系,用不同种类的 Loader 将各种类型的文件转成webpack可识别的模块(js json模块)。plugin 用于扩展和增强webpack打包的能力,例如,生成HTML文件、压缩代码、提取公共代码等。webpack 启动本地开发服务是先将所有模块全部编译打包好(bundle),再启动服务,当项目越来越大越复杂的时候,启动就会变慢。
vite 是新一代开发构建工具。vite 基于es module开发,利用现代浏览器对原生 es 模块的支持实现快速启动和热更新。vite 先启动本地web开发服务器,当浏览器发起请求时(import)再解析打包对应的源码模块。首次启动时还会将依赖使用esbuild(巨快的js打包器)预构建。生产构建基于 rollup(生态成熟的es模块打包工具,主要用于类库打包)。
热更新
webpack 热更新
首先 webpack-dev-server 启动本地编译,并启动服务,同时还启动了一个 websocket 服务,将 websocket 双端通信代码文件写入到 entry 入口中,这样代码在浏览器运行时就建立了双向通信。然后用 webpack-dev-middleware 插件开启编译并在编译结束后开启监听,当文件发生变化,重新编译,并向客户端发出通知检查下是否需要热更新。
vite 热更新
vite dev server 会在 index.html
中,注入路径为 @vite/client
的脚本,当浏览器访问 index.html
时,就会拉取该脚本创建 websocket 通信。当源代码改动时,dev server 将热更新边界信息传给浏览器,浏览器重新import()改动的模块,vite就会重新编译并返回更新后的模块代码。
babel
Babel的作用就是把当前项目中使用的ES 6、ES 7和JSX等语法,转译为当下可以执行的JavaScript版本,让浏览器能够识别
babel-loader
是一个Webpack的加载器,用于在打包过程中转译JavaScript代码(如es6=>es5,语法向下兼容)。
babel-core
词法分析-语法分享-语义分析,生成ast抽象语法树。
babel-preset-env
根据当前配置环境自动确定需要使用的Babel插件和转译规则。
@babel/polyfill
和 @babel/runtime
的区别:前者改造目标浏览器,让你的浏览器拥有本来不支持的语法特性;后者改造你的代码,让你的代码能在所有目标浏览器上运行,但不改造浏览器。
import moduleName from 'xxModule' 和 import('xxModule')经过webpack编译打包后最终变成了什么?在浏览器中是怎么运行的?
所有 import
经过webpack
打包以后变成Map
对象,key
为模块路径,value
为模块的可执行函数;在代码执行时调用webpack自己的方法__webpack_require__执行key
对应的模块函数。 import('xxModule')
会将模块打包成一个单独的js文件,在请求时加载并执行。
webpack 文件拆分
- bundle splitting 将代码拆分成多个bundle文件,有效利用浏览器缓存。
- 配置optimization.splitChunks.chunks = 'all'可将代码打包成main(源代码)+vendor(依赖)。
- 也可以配置 optimization.splitChunks.cacheGroups.vendor,将所有npm依赖包拆分单独的文件。(十几个请求还好)
- 还可以将业务代码的公共部分拆分
- code splitting 动态加载代码,用户可以仅下载他们正在查看的网站部分所需的代码。(import(..))
webpack 性能优化
目的:
- 减小打包后的文件大小
- 首页按需引入文件
- 优化 webpack 打包时间
方法:
- 按需加载(路由、组件懒加载、依赖按需加载、依赖不放在main.ts)
- 优化loader配置(设置include,exclude,cacheDirectory)
- 拆包、提取公共代码(合理利用缓存)
- treeshaking(已经内置支持)
- cdn优化,公共依赖走cdn(vue/vue-router/vuex/swiper/ui库)
- 代码压缩并文件压缩,gzip
- 生产关闭sourcemap
- 开启多线程解析处理文件(HappyPack)
网络协议和缓存
storage
都必须遵循浏览器同源策略。同源即scheme(协议)、host(域名或 IP 地址)、port(端口)相同。 cookie小、在有效期内有效、每次网络请求都携带、顶级域名共享; localStorage大、持久有效; sessionSotrage只在当前浏览器tab页内有效。
cookie-session、sso、token、jwt
http是无状态协议,为了记录用户的状态,就需要一种方式让服务端知道客户端的身份。
cookie-session 是一种利用cookie缓存用户状态达到身份识别的模式。用户登录后,服务端生成、保存 sessiion 并下发 sessionId,将 sessionId 缓存到客户端的 cookie 中,之后客户端的每次请求都会携带该 cookie 以证明用户身份。服务端需要存储维护 session 相关信息,以便在客户端请求时根据sessionId 验证并查询对应的session信息。
sso即单点登录,由于 cookie 可以设置顶级域名共享,所以在同一顶级域名下的子系统都可以共用同一个 cookie 实现单点登录。
由于 cookie 在移动端的支持并不好,所以移动端一般利用 token 来实现身份验证。用户登录后,服务端根据用户信息生成加密 token 并下发,客户端存储 token ,并在之后的每次请求 header 中携带。服务端收到请求后解析验证 token 。(简单 token 的组成: uid用户唯一的身份标识、time当前时间的时间戳、sign签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
jwt 是token 令牌的一种。token 模式下,服务端收到请求后需要查库来验证 token 是否有效。jwt 模式下,服务端收到请求后用密钥解密并验证签名信息,并且还能直接获取用户部分信息,减少数据库查询。
缺点: 由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token。(除非服务端部署额外的逻辑)
Cookie和跨域的关系
简单请求(head/get/post,三种content-type,无自定义请求头)下,服务端设置 Access-Control-Allow-Origin: *
即可实现跨域。
复杂请求下,服务端的 Access-Control-Allow-Origin: xxx
必须设置为特定请求域名来实现跨域。
跨域如果要携带cookie,客户端需要设置 withCredentials: true
,cookie 也要设置 SameSite=None; Secure
。Secure必须是https。
强缓存和协商缓存
浏览器的缓存机制分为强缓存和协商缓存。强缓存是直接从缓存中读取资源,不会向服务器发送请求(网络面板显示from disk cache或from memory cache)。协商缓存是强缓存失效后,浏览器携带缓存标识重新请求,服务端根据标识判断是否使用缓存的过程。
接口的响应头 Response Header 中带有 expires cache-control 字段的指定过期时间和时长。下次接口请求时如果没过期,则直接使用本地缓存,不向服务端发起请求,这就是强缓存。 如果过期了,则是协商缓存。协商缓存分为两种情况,接口请求时携带上次请求的缓存标识,服务端如果判断没过期,则返回200,浏览器直接使用缓存。如果过期了,则返回304并返回资源,浏览器重新缓存资源。 这篇
css、js、dom间的阻塞问题
html页面的渲染过程是document解析为DOM,同时css解析为CSSOM,两者合并生成rander tree,开始布局渲染。
css并不会阻塞dom的解析,但是render tree的生成依赖CSSOM,也就是css会阻塞页面渲染。 如果css的link标签后紧接着script标签,那么js会等待CSSOM的解析完成才执行,而js是阻塞渲染引擎的,所以这时相当于css阻塞了整个页面的解析和渲染。
js完全阻塞页面的解析和渲染。但是 defer 会并行下载不会阻塞页面解析,而会在dom解析完成之后,且js下载完成后才会执行。async 也并行下载,但是在下载完成后是立即执行,所以多个 async 是无序的,谁先下载完谁先跑。
# 原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的