1、vuex的优缺点
优点:
能够在vuex中,集中管理共享的数据,易于开发和后期维护;
Vuex 的状态存储是响应式的,当 Vue 组件从 store中读取状态的时候,若 store 中的状态发生变化,能够触发响应式的渲染页面更新 (localStorage就不会),那么相应的组件也会相应地得到高效更新。
js 原生的数据对象写法, 比起 localStorage 不需要做转换, 使用方便
限定了一种可预测的方式改变数据, 避免大项目中, 数据不小心的污染
缺点
刷新浏览器,vuex中的state会重新变为初始状态;
解决方案-插件 vuex-persistedstate
2、如何减少http请求的次数
- 能做雪碧图就做雪碧图
- base64编码(把一张小于多少大小的图片变成一种64位编码的形式)
因为编码就不用请求,就直接解析成一张图片
坏处是增加了数据量,增加了请求时间
只建议用内存比较小的图标采取这种方式 - 合并脚本与样式表代码
HTML/JS/CSS - 缩小CSS和JavaScrit文件
你用你的域名在第三方服务器上进行解析,从而生成CDN加速域名(例:七牛云) - 尽量使用浏览器的缓存机制
- 实施延迟加载技术
- 减少外部脚本的数量
- 使用内容分发网络(CDN)
CDN是位于世界各地的服务器网络。CDN在您的网站上缓存静态资源,然后用户访问您的网页时,将其提供给缓存的内容。缓存的内容是从服务器交付的,最接近用户的物理位置。
是否要使用CDN取决于几件事,最重要的是您的流量是本地流量还是国际流量。如果您的大多数网站访问者是本地访问者,则不需要内容交付网络(CDN加速)
3、px、em、rem的区别
一、px是固定的像素,一旦设置了就无法因为适应页面大小而改变。
二、em和rem相对于px更具有灵活性,他们是相对长度单位,意思是长度不是定死了的,更适用于响应式布局。
三、em是相对于其父元素来设置字体大小的,一般都是以
的“font-size”为基准。这样就会存在一个问题,进行任何元素设置,都有可能需要知道他父元素的大小。而Rem是相对于根元素,这样就意味着,我们只需要在根元素确定一个参考值。总之:对于em和rem的区别一句话概括:em相对于父元素,rem相对于根元素。
4、css优先级算法
选择器的优先级顺序是由各个选择器的权重决定的。具体如下表:
| 选择器 | 权重值 |
|---|---|
| !important | infinity(无穷大) |
| 行内样式 style” “ | 1000 |
| id选择器 | 100 |
| class类选择器、属性选择器、伪类选择器 | 10 |
| 标签(元素)选择器 | 1 |
| 通配符(*)选择器 | 0 |
- 优先级就近原则,同权重情况下样式定义最近者为准
- 载入样式以最后载入的定位为准
- 优先级为:
!important > 行内style > id > class > tag;
5、写出代码打印结果
1 | var name = 'World'; |
变量提升:在 JavaScript 中,使用 var 声明的变量会被提升到函数的顶部。这意味着在函数内部,var name 的声明会在 if 语句之前被处理,但赋值不会被提升。
作用域:在 IIFE 中,var name 声明了一个局部变量 name,这会遮蔽(shadow)外部的 name 变量。
typeof 检查:在 if 语句中,typeof name 返回的是 ‘undefined’,因为在此时局部变量 name 已经被声明但尚未赋值(提升后,局部变量 name 的值为 undefined)。
执行结果:由于 if 条件为真,执行了 var name = ‘AJ’;,然后打印 HiAJ。
1 | function changeObhProperty(o) { |
对象的引用:在 JavaScript 中,对象是通过引用传递的。当你将 webSide 传递给 changeObhProperty 函数时,函数内部的 o 变量指向的是同一个对象。
修改属性:在函数的第一行,o.siteUrl = ‘https://www.ke.com'; 直接修改了传入对象的 siteUrl 属性,因此 webSide.siteUrl 现在是 https://www.ke.com。
重新赋值:在第二行,o = new Object(); 创建了一个新的对象并将 o 指向这个新对象。这并不会影响到外部的 webSide 对象,因为此时 o 只是一个局部变量,指向了一个新的对象。
局部作用域:在函数内部,o 的重新赋值只影响了函数内部的 o 变量,而不会影响到外部的 webSide 对象。
最终输出:因此,当你在函数外部打印 webSide.siteUrl 时,它仍然是 https://www.ke.com,因为 webSide 对象的属性在函数内部的重新赋值并没有改变它。
6、浏览器的重绘与回流
浏览器的渲染原理:
1.解析HTML,生成DOM树,解析CSS,生成CSSOM树
2.将DOM树和CSSOM树结合,生成渲染树(Render Tree)
回流:
回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致了其所有子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流。
重绘:
重绘是由于节点的几何属性发生改变或者由于样式发生改变但不会影响布局。例如outline, visibility, color、background-color等,重绘的代价是高昂的,因为浏览器必须验证DOM树上其他节点元素的可见性。
什么时候发生回流
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
- 页面一开始渲染的时候(这肯定避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
回流一定会触发重绘,而重绘不一定会回流
1、CSS优化法
- 使用 transform 替代 top
- 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局
- 避免使用table布局,可能很小的一个小改动会造成整个 table 的重新布局。
- 尽可能在DOM树的最末端改变class,回流是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。
- 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
- 将动画效果应用到position属性为absolute或fixed的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 requestAnimationFrame。
- 避免使用CSS表达式,可能会引发回流。
- 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,例如will-change、video、iframe等标签,浏览器会自动将该节点变为图层。
- CSS3 硬件加速(GPU加速),使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。
2、JavaScript优化法
- 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
- 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
7、节流和防抖
- 节流
节流是在一段时间内只运行一次,若在一段时间内重复触发,只有一次生效。(场景示例:点击按钮登录)
1 | function throttled(fn, delay = 500) { |
- 防抖
防抖是在一段时间后再执行操作,若在一段时间内被重复触发,则重新计时。(场景示例:搜索框自动执行模糊匹配)
1 | function dobounce(fn, delay) { |
8、什么是缓存,及其作用
缓存其实就是一个临时的存储器。缓存有 :cookie、session、application、cache、redis
缓存主要是为了提高数据的读取速度。因为服务器和应用客户端之间存在着流量的瓶颈,所以读取大容量数据时,使用缓存来直接为客户端服务,可以减少客户端与服务器端的数据交互,从而大大提高程序的性能。
以前实现数据的缓存有很多种方法,有客户端的Cookie,有服务器端的Session和Application。其中Cookie是保存在客户端的一组数据,主要用来保存用户名等个人信息。Session则保存对话信息。Application则是保存在整个应用程序范围内的信息,相当于全局变量。通常使用最频繁的是Session,缓存也是有限的,会自动清除之前的旧数据。其中redis的读取速度最快,并且是在内存中进行读取,当内存不够时可以扩大内存,还有就是 .net提供的Cache缓存。
强制缓存和协商缓存
区别:
- 强制缓存在缓存有效的情况下不会去请求服务器, 其数据来源则是浏览缓存的本地磁盘。而协商缓存会向服务器请求,但是在协商缓存成功的情况下, 服务器只会返回一个不带响应体的报文
- 强制缓存在浏览器强制刷新的情况下不会生效, 而协商缓存则不受影响。(调试代码测试时候,要注意)
- 强制缓存返回的报文状态码为 200, 协商缓存返回的报文状态码为 304 (前端使用fetch请求的情况, 协商缓存的 状态码304 会转成 200)
- 强制缓存发生在浏览器端, 协商缓存发生在服务器端。
前端缓存策略
- 强缓存:通过设置HTTP响应头中的Expires和Cache-Control字段来控制资源缓存的有效期。如果资源未过期,浏览器直接从缓存中加载资源,不会向服务器发送请求。Expires是一个过期时间戳,而Cache-Control提供了更灵活的选项,如
max-age,它表示资源在多长时间内有效。 - 协商缓存:通过设置Last-Modified和ETag响应头来标识资源的版本。浏览器在发起请求时,通过If-Modified-Since和If-None-Match请求头将这些信息发送给服务器。如果资源未发生变化,服务器返回304状态码,告知浏览器继续使用缓存。ETag比Last-Modified更准确,因为它基于文件的唯一标识符。
- Service_Worker缓存:使用Service Worker可以实现更灵活的缓存控制,包括离线运行和更细粒度的缓存策略。通过在Service Worker中定义缓存策略,可以实现自定义的缓存行为。
- localStorage和sessionStorage:除了缓存静态资源,还可以使用localStorage和sessionStorage存储动态数据。这些数据可以在页面刷新后依然保持,减少对服务器的请求。
- CDN缓存:使用内容分发网络(CDN)可以将静态资源缓存在离用户较近的服务器节点上,加快资源加载速度。
缓存策略的应用场景和优势:
- 应用性能:提供更快的响应时间,减少服务器端的负载。
- 用户体验:提高页面加载速度,减少用户等待时间。
- 成本效益:通过减少网络传输时间和服务器负载,降低运营成本。
缓存策略的优化建议:
- 根据具体的应用场景和资源特性选择合适的缓存策略。
- 定期清理无效缓存,避免占用过多存储空间。
- 使用现代浏览器提供的缓存API,如Service Worker,实现更精细的缓存控制。
9、封装一个异步加载图片的方法
1 | loadImageAsync("./loadImg.jpg").then(image => document.body.appendChild(image)) |
10、webpack中loader和plugin的区别
loader从字面的意思理解,是加载的意思。由于webpack 本身只能打包js文件,所以,针对css,图片等格式的文件没法打包,就需要引入第三方的模块进行打包。loader虽然是扩展了 webpack ,但是它只专注于转化文件(transform)这一个领域,完成压缩,打包,语言翻译。loader是运行在NodeJS中。仅仅只是为了打包。
如:css-loader和style-loader模块是为了打包css的
babel-loader和babel-core模块时为了把ES6的代码转成ES5
url-loader和file-loader是把图片进行打包的。
plugin也是为了扩展webpack的功能,但是 plugin 是作用于webpack本身上的。而且plugin不仅只局限在打包,资源的加载上,它的功能要更加丰富。从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。webpack提供了很多开箱即用的插件.
12、地址栏输入url 发生了什么
- 在浏览器中输入一个url
- 根据域名解析出ip地址(dns的过程)
(1)先检查浏览器缓存,如果有,返回,没有,下一步
(2)检查系统缓存,hosts文件
(3)检查网络中路由器的dns缓存
(4)递归查询,不优先查找浏览器所在的本地域名服务器,先查找其他域名服务器,看有没有,如果没有,再查找本地域名服务器,本地域名服务器用迭代查询来查找
(5)迭代查询,不断向上访问,查找 - 三次握手建立tcp连接
- 发送http请求
- 如果发生重定向,状态码是3开头,那么返回第一步,继续匹配重定向的服务器
- 服务器处理请求,并且服务器发送html响应
- 浏览器收到http响应,tcp断开连接
- 如果得到的资源(静态)可以缓存,进行缓存
接着执行如下过程
52、浏览器渲染原理
当浏览器的网络线程获取到 HTML 文档开始,浏览器会生成一个渲染任务,并将这个渲染任务放到消息队列下,当事件循环机制轮到此任务时,再提取到渲染主线程,开始渲染流程。这个过程涉及多个阶段,分别是: HTML 解析、样式计算、布局、分层、绘制、分块、光栅化、绘画。每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入。整个渲染流程形成了一套严密的生产流水线,以确保页面能够快速、准确地呈现给用户。
1.解析 HTML
浏览器在解析 HTML 之前可能会启动一个预解析线程,提前下载 HTML 中引用的外部资源,如 CSS 和 JavaScript 文件。
如果主线程解析到link位置 ,外部 CSS 文件会被异步下载和解析,而不会阻塞 HTML 解析过程。这是因为 CSS 不会改变文档结构,可以并行处理,从而加速页面加载。
相反,外部 JavaScript 文件的下载和执行可能会修改 DOM 结构,因此浏览器会暂停 HTML 解析,等待 JavaScript 文件的下载和执行完成后再继续解析 HTML。这样可以确保在执行 JavaScript 代码之前,DOM 已经完全解析,避免了可能的页面渲染问题。
浏览器解析完成后会得到 DOM 树和 CSSOM 树,DOM 树表示文档的结构和内容,外部 CSS 文件构建 CSSOM 树,表示文档的样式信息。
2.样式计算
在样式计算过程中,主线程会遍历得到的 DOM 树,并匹配文档中的每个元素与对应的 CSS 规则,经过选择器匹配、继承处理和优先级计算后,浏览器会得到每个元素的最终样式属性,称之为 Computed Style。
3.布局
布局阶段,浏览器会依次遍历 DOM 树的每一个节点 ,并根据每个元素的位置和大小计算出它们在页面中的准确位置,这个过程也被称为重排。
4.分层
分层指的是将页面中的元素按照一定规则分成多个图层,每个图层可以独立地进行绘制和渲染,以提高页面渲染性能和用户体验。
分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。
5.绘制
在绘制阶段下,渲染主线程会负责生成每个图层的绘制指令集,描述了如何将每个图层的内容绘制出来。这些绘制指令集包括了元素的位置、大小、颜色、阴影等信息,以及如何对页面元素进行变换、裁剪等操作。最后将渲染树中的每个元素转换为屏幕上的实际像素,形成绘制表面(Paint Surface),这个过程也称为重绘(Repaint)。
完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。
6.分块
分块是指将页面的图层划分为多个小块区域的过程。这个过程是由合成线程负责的,它会从线程池中拿取多个线程来完成分块工作。
分块的方法通常是根据图层的大小和复杂度来确定的。较大或较复杂的图层可能会被划分为更多的小块,而较小或简单的图层则可能只需划分为少数几个小块。分块的大小和数量通常会根据设备的性能和屏幕的分辨率进行调整,以实现最佳的渲染性能。
分块过程通常会考虑到每个小块的尺寸、图层的边界以及图层之间的关系,以确保分块后的小块能够正确地覆盖整个图层。
7.光栅化
光栅化是指将图形对象转换为屏幕上的像素也就是位图,矢量图形(如线条、路径等)也会被转换为像素图形。并且该过程支持硬件加速,即利用 GPU 等硬件资源加速光栅化过程,提高渲染效率和性能。
在进行光栅化时,通常会采取一些优化策略,其中一个重要的优化策略是优先处理靠近视口的块。这是因为靠近视口的块对用户来说更为重要,它们在屏幕上的可见性更高,因此优先处理这些块可以提高页面的显示速度和用户体验。
8.合并
合并是渲染的最后一个阶段,是指将多个图层的内容合并成最终的页面图像的过程。在合并阶段,浏览器会将经过光栅化处理的各个图层按照一定的顺序进行组合,并交给 GPU 进⾏,形成最终的渲染结果。
13、js css 顺序对前端优化影响
渲染树的构成必须要 DOM 树和 CSSOM 树的,所以尽快的构建 CSSOM 树是一个重要的优化手段,如果 css 文件放在尾部,那么整个过程就是一个串行的过程先解析了 dom,再去解析 css。所以 css 我们一般都是放在头部,这样 DOM 树和 CSSOM 树的构建是同步进行的。
再来看 js,因为 js 的运行会阻止 DOM 树的渲染的,所以一旦我们的 js 放在了头部,而且也没有异步加载这些操作的话,js 一旦一直在运行,DOM 树就一直构建不出来,那么页面就会一直出现白屏界面,所以一般我们会把 js 文件放在尾部。当然放到尾部也不是就没有问题了,只是问题相对较小,放到尾部的 js 文件如果过大,运行时间长,代码加载时,就会有大量耗时的操作造成页面不可点击,这就是另一个问题,但这肯定比白屏要好,白屏是什么页面都没有,这种是页面有了只是操作不流畅。
js 脚本放在尾部还有一个原因,有时候 js 代码会有操作 dom 节点的情况,如果放在头部执行,DOM树还没有构建,拿不到 DOM 节点但是你又去使用就会出现报错情况,错误没处理好的话页面会直接崩掉。
14、js事件循环机制
首先,JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环。javascript代码在执行的时候,是有几个“通道”的。
首先是调用栈,执行耗时较短的操作,耗时较长的操作先放置到任务队列中,任务队列又分为宏任务(macro-task)和微任务(micro-task),微任务中队列中放置的是 promise.then、async、await 这样操作,宏任务队列中放置的是 setTimeout、ajax、onClick事件,等调用栈的任务执行完成再轮询微任务队列,微任务队列中任务执行完成之后再执行宏任务。
这里提到了栈和队列,简单说一下这两种数据结构,栈是一种后进先出的结构,只能从尾部进入,从尾部删除,拿生活中的场景来打比方,就好像自助餐的餐盘,最先放的盘子在最底下,最后放的盘子在最上面,需要把最上面的盘子一个个拿走,才能拿到最下面的盘子。
而队列,是一种先进先出的结构,从尾部进入,从头部删除,就像我们去排队买东西,先去的同学可以先买到。
再回到事件循环机制(event loop),不阻塞主进程的程序放入调用栈中,压入栈底,执行完了就会弹出,如果是函数,那么执行完函数里所有的内容才会弹出,而阻塞主进程的程序放入任务队列中,他们需要“排队”依次执行。
在JavaScript中,所有的任务都可以分为
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如
ajax网络请求,setTimeout定时函数等
同步任务与异步任务的运行流程图如下:

从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环
宏任务与微任务
微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
- Promise.then
- MutaionObserver
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务宏任务
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
这时候,事件循环,宏任务,微任务的关系如图所示

按照这个流程,它的执行机制是:
- 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
- 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完
15、new操作符工作原理
- 创建空对象,作为将要返回对象的实例
- 将空对象的
__proto__指向构造函数的prototype属性 - 将空对象赋值给函数中的this
- 开始执行构造函数,返回一个object
16、vue中Diff算法、key的作用原理
- Diff算法步骤:
- 用js对象结构(虚拟DOM)表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
- 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
- 把所记录的差异应用到所构建的真正的DOM树上,视图就更新了
- key的作用原理:
- 唯一标识,为了高效的更新虚拟DOM
- transition过渡时,使用key属性,可以区分它们是否变化,否则vue只会替换其内部属性而不会触发过渡效果
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
17、CSRF攻击的原理及解决方案
csrf攻击原理:
攻击者利用xss方式注入一段脚本,当受害者在浏览器中运行该脚本时,脚本仿冒受害者,向合法的web系统发送一个请求,这个请求会被web系统当做受害者主动提出的合法请求,进而利用合法用户的身份执行攻击者指定的操作。csrf攻击解决方法:
目前,防御CSRF攻击主要有三种策略:
1、验证HTTP Referer 字段;
2、在请求地址中添加token并验证;
3、HTTP头中自定义属性并验证。
18、MVVM和MVC的区别
MVC
- M:模型层,是应用程序中用于处理应用程序数据逻辑的部分,模型对象负责在数据库中存取数据。
- V:视图层,是应用程序中处理数据显示的部分,视图是依据模型数据创建的
- C(Controller): 控制层,是应用程序中处理用户交互的部分,控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理,它只是接收请求并决定调用那个模型构建去处理请求,然后在确定用哪个视图来显示返回的数据。
MVVM
- M:模型层,就是业务逻辑相关的数据对象,通常从数据库映射而来,我们可以说是与数据库对应的model。
- V:视图层。展示出来的用户界面。
- VM: 视图模型层,连接view 和 model 的桥梁。因为 model层中的数据往往是不能直接跟 view 中的控件一一对应上的,所以需要在定义一个数据对象专门对应 view 上的控件,而 viewModel 就是把 model 对象封装成可以显示和接受输入的界面数据对象。
view 和 viewModel 之间通过双向数据绑定建立联系,这样当 view 变化时,会自动更新到 viewModel,反之亦然
mvvm的优势:
- mvc和 mvvm 都是一种设计思想,主要就是 mvc 中 controller 演变成 mvvm 中的 viewModel。mvvm 主要解决了 mvc 中大量 dom 操作使页面 渲染性能降低,加载速度变慢的问题
- mvvm 和 mvc 最大的区别就是:它实现了 view 和 Model 的自动同步,当 model 的属性改变时,我们不用在手动操作 dom 元素来改变 view 的显示,它会自动变化
备注:MVC是Model-View-Controller的简写。即模型-视图-控制器。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。使用MVC的目的就是将M和V的代码分离。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。MVC和MVVM的区别并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,只不过是弱化了C的概念,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用,使开发更高效,结构更清晰,增加代码的复用性。
19、前端性能优化方法
渲染优化
- 减少回流和重绘
加载优化
- 第三方模块使用CDN方式
- 大模块异步加载,require.ensure
- 零散的小模块合并一起加载
使用entry指定文件入口,或者头部用import/require建立依赖关系 - 使用预加载Prefetch,适用于分步场景
图片优化
- 小图片用Sprite(图片精灵技术)整合成一张图片,减少并发次数,base64内联。
- 图片懒加载
- 使用webp格式
- 压缩图片(
image-minimizer-webpack-plugin:用来压缩图片的插件) - 使用srcset,配置不一样的分辨率对应不同的图片大小,能精准对应不同的尺寸用户
css优化
- css写在头部
假如css写在底部,浏览器会一直等待css全部加载完毕,才能开始解析。
而且下载解析css完毕后,已经呈现的文字和图片需要根据新的样式重绘。 - 避免css表达式
- 移除没用的css规则
- 减少行内样式
js优化
- js放在body底部
这样做无需担心页面未完成加载获取不到dom,也能避免脚本运行缓慢造成页面卡死。 - js用defer放在头部,不会阻塞dom解析
只对外部脚本文件有效,外部js文件和当前html页面同时加载(异步加载),但只在当前页面解析完成之后执行js代码
11、webpack优化配置
1、开发环境性能优化
- 优化打包构建速度
- HMR
1 | 一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块) 极大提升构建速度 |
- 优化代码调试
- source-map
1 | 一种 提供源代码到构建后代码映射 技术 (如果构建后代码出错了,通过映射可以追踪源代码错误) |
2、生产环境性能优化
- 优化打包构建速度
oneOf
在 Webpack 中,oneOf 是一种配置方式,用于优化模块的处理速度。它允许您在一个规则中定义多个处理器,Webpack 会从上到下依次匹配,找到第一个匹配的规则并停止匹配。这种方式可以提高构建速度,因为它避免了对所有规则的遍历。babel缓存
babel对我们写的js代码做编译处理时,假设有100个js模块,一个模块变,只变这一个模块,其他模块不变。
开启babel缓存,之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程。多进程打包
Thread-loader:Webpack 官方出品,以多进程方式运行资源加载逻辑externals
不想把第三方库打包到bundle中,这就有了externals。dll
dll的作用是将项目中一些不常改变的依赖单独打包
- 优化代码运行的性能
- 缓存(hash、chunkhash、contenthash)
在 Webpack 中,缓存机制是通过为生成的文件添加哈希值来实现的,以便浏览器能够判断文件是否发生了变化。以下是对 hash、chunkhash 和 contenthash 的直观解释:
1. 缓存机制的目的
优化加载速度:浏览器会缓存资源,以避免每次都从服务器下载相同的文件。如果文件没有变化,浏览器会直接使用本地缓存的版本。
避免缓存问题:当文件内容发生变化时,我们希望浏览器能够下载新的文件,而不是使用旧的缓存。
2. 哈希值的类型
hash
定义:hash 是基于整个 Webpack 构建过程生成的哈希值。它代表了整个项目的构建状态。
特点:
如果项目中的任何文件发生变化(无论是 JS、CSS 还是其他文件),hash 值都会改变。
这意味着如果您修改了一个 CSS 文件,所有依赖于 hash 的文件名都会改变,包括 JS 文件。
问题:这种方式不够灵活,因为即使您只修改了 CSS,所有文件的缓存都会失效,导致不必要的重新下载。
chunkhash
定义:chunkhash 是基于特定代码块(chunk)的内容生成的哈希值。每个代码块可以包含多个模块。
特点:
如果您只修改了某个特定的 JS 文件,只有与该文件相关的代码块的 chunkhash 会改变,而其他代码块的 chunkhash 保持不变。
这使得缓存更具灵活性,因为只有受影响的文件会被重新下载。
适用场景:适合用于 JS 文件,因为它们通常会有多个代码块。
contenthash
定义:contenthash 是基于文件内容生成的哈希值,专门用于 CSS 和其他静态资源。
特点:
只有当文件内容发生变化时,contenthash 才会改变。
这意味着如果您只修改了 CSS 文件,只有该 CSS 文件的 contenthash 会改变,而 JS 文件的 chunkhash 和 hash 不会受到影响。
适用场景:适合用于 CSS 文件和其他静态资源,确保它们的缓存机制独立于 JS 文件。
总结
hash:基于整个构建过程,修改任何文件都会改变哈希值,导致所有文件的缓存失效。
chunkhash:基于特定代码块的内容,修改某个模块只会影响相关的代码块,其他代码块的缓存保持有效。
contenthash:基于文件内容,只有文件内容变化时才会改变,适合用于 CSS 和静态资源,确保它们的缓存独立于 JS 文件。
通过合理使用这三种哈希值,您可以优化资源的缓存机制,提高用户体验,减少不必要的网络请求。
- tree shaking(摇树)
用于描述移除 JavaScript 上下文中的未引用代码
Tree Shaking 是一个术语,在计算机中表示消除死代码,依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)
在webpack实现Trss shaking有两种不同的方案:- usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的
- sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用
- code split(代码拆分 - splitChunksPlugin)
将一个文件打包生成多个文件,这样做可以将一个大的文件分割成多个小的文件同时并行加载。同时分割成多个文件还可以实现按需加载的功能
将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件
默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度
代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能
这里通过splitChunksPlugin来实现,该插件webpack已经默认安装和集成,只需要配置即可
默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all
懒加载/预加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//懒加载或者按需加载,会在文件需要使用时才加载,是一种很好的优化网页或应用的方式。
document.getElementById('btn').onclick = function() {
// 懒加载~:当文件需要使用时才加载~
import(/* webpackChunkName: 'test'*/'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};
//在声明 import 时,使用webapck的内置指令/* webpackPrefetch: true */就可以对指定资源进行预加载
document.getElementById('btn').onclick = function() {
// 预加载 prefetch:会在使用之前,提前加载js文件
// 正常加载可以认为是并行加载(同一时间加载多个文件)
// 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};pwa(渐进式网络开发应用程序(离线可访问)
它让我们的应用程序可以像 APP 一样,离线也能访问,性能也更好。但是由于兼容性问题,现在还没有大面积推广开来(但是大厂有使用,比如说淘宝)。要在项目中真正使用PWA,我们需要借助 workbox 这个库,而在 webpack 中,我们还需要引入 workbox-webpack-plugin 这个插件。JS代码压缩 (terser-webpack-plugin)
terser是一个JavaScript的解释、绞肉机、压缩机的工具集,可以帮助我们压缩、丑化我们的代码,让bundle更小CSS代码压缩 (css-minimizer-webpack-plugin)
CSS压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等Html文件代码压缩 (HtmlWebpackPlugin)
使用HtmlWebpackPlugin插件来生成HTML的模板时候,通过配置属性minify进行html优化文件大小压缩 (compression-webpack-plugin)
对文件的大小进行压缩,减少http传输过程中宽带的损耗图片压缩 (image-webpack-loader)
一般来说在打包之后,一些图片文件的大小是远远要比 js 或者 css 文件要来的大,所以图片压缩较为重要内联chunk (InlineChunkHtmlPlugin)
可以通过InlineChunkHtmlPlugin插件将一些chunk的模块内联到html,如runtime的代码(对模块进行解析、加载、模块信息相关的代码),代码量并不大,但是必须加载的
20、函数柯里化
1 | // 函数柯里化,利用递归和闭包实现 |
21、AST树
AST树主要是用来做语法抽象的,然后把语法按照树形结构抽象出来,标记出来个各节点,知道节点有什么好处,可以根据自己的需要,比如要对dom节点,或者test节点,或一些方法节点做一些操作时,通过AST,再结合一些线程插件,生成一个.json文件,通过这个文件对整个项目进行分析。
第一可以优化项目,或者做一些loader或者plagin时,可以用到AST。再者做一些文件的转换、代码的转编译,也可以用到AST。
一些bug的底层或者webpack的底层,都是通过这么一个方式去做的。
22、为什么mutation必须是同步函数,actions 可以处理异步函数
如果我们在mutation中写了异步,commit在触发mutation事件时,异步的回调函数不知道是什么时候执行的,所以在devtools中难以追踪变化。
actions 可以做异步操作,但是并不是直接修改数据,而是通过提交mutations 里面的方法。
这样在devetools就可以追踪到状态的变化。
23、vue中$set的原理
当$set所设置的目标对象为数组时,则调用目标对象的splice方法将修改的数据变为响应式。
当$set所设置的目标对象为对象时,首先判断这个属性是否在这个对象上,如果存在则设置属性为对应的属性值后直接返回val,然后判断目标对象是否为Vue实例或者根数据对象,如果是则warn警告后返回,再去判断这个目标对象是否是响应式的,如果不是响应式对象则直接赋值返回。最后在给目标对象的属性添加响应式,通知dep实例的所有订阅者进行更新。
24、AMD和CMD的区别
最主要的是:
AMD是预加载,CMD是懒加载。AMD是提前执行,CMD是延迟执行。
amd (在对应的加载之前导入),cmd(在用的时候导入)。
以下为相关补充:
AMD
AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。
它是一个在浏览器端模块化开发的规范由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。
requireJS主要解决两个问题:
1、多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
2、js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
CMD
CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同。
因为CMD推崇一个文件一个模块,所以经常就用文件名作为模块id
CMD推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写。
factory是一个函数,有三个参数,function(require, exports, module)
1.require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口:require(id)
2.exports 是一个对象,用来向外提供模块接口
3.module 是一个对象,上面存储了与当前模块相关联的一些属性和方法
25、loader
loader主要导出一个函数,它接收三个参数:content、sourceMap、meta,content代表文件处理的信息,基本去用loader处理一些scss、es6,第一个就代表文件信息,这个文件信息也可以通过指定row-loader把它变成原文件的形式。经常使用的是loader中的this,因为this里面存着一些webpack内置的方法,快速的实现一些功能。
27、跨域
跨域问题是出于浏览器的安全策略考虑的,跨域它首先有三点:第一点需要在浏览器当中,第二点需要请求后端接口,第三点是触发了同源策略,同源策略是浏览器的一种自我保护机制,它要求三点:域名、协议、端口号,三点一旦触发任何一种不同,都会出现跨域问题。
解决方案:1. 通过后端配置请求头; 2. 本地服务器开启一个代理; 前者在实际项目当中,后者在本地开发当中。
28、纯函数
对于函数式编程,所引申出来的一个概念。他要求函数一个单独的入参同时有一个单独的返回值,对于函数体外部的内容,没有任何影响。函数式编程也是编程的一个常见的编程范式,同时还有面向对象、面向过程。纯函数它的函数是作为一个一等公民,他是第一位的,另外建议你去使用表达式,不使用函数语句,同时纯函数里的内容不可修改,引用的时候清晰透明。好处是容易维护,对外界的影响小。
30、如何给SPA做SEO
下面给出基于Vue的SPA如何实现SEO的三种方式
- SSR服务端渲染
将组件或页面通过服务器生成html,再返回给浏览器,如nuxt.js - 静态化
目前主流的静态化主要有两种:
(1)一种是通过程序将动态页面抓取并保存为静态页面,这样的页面的实际存在于服务器的硬盘中
(2)另外一种是通过WEB服务器的 URL Rewrite的方式,它的原理是通过web服务器内部模块按一定规则将外部的URL请求转化为内部的文件地址,一句话来说就是把外部请求的静态地址转化为实际的动态页面地址,而静态页面实际是不存在的。
这两种方法都达到了实现URL静态化的效果 - 使用Phantomjs针对爬虫处理
原理是通过Nginx配置,判断访问来源是否为爬虫,如果是则搜索引擎的爬虫请求会转发到一个node server,再通过PhantomJS来解析完整的HTML,返回给爬虫。下面是大致流程图
31、vue实例挂载的过程
- new Vue的时候调用会调用_init方法
- 定义 $set、$get 、$delete、$watch 等方法
- 定义 $emit、$on、$off 等事件
- 定义 _update、$forceUpdate、$destroy生命周期
- 调用$mount进行页面的挂载,主要是通过mountComponent方法
- 定义updateComponent更新函数
- 执行render生成虚拟DOM
- _update将虚拟DOM生成真实DOM结构,并且渲染到页面中
32、首屏加载速度慢解决方案
减小入口文件体积
静态资源本地缓存
UI框架按需加载
图片资源的压缩
防止组件重复打包
解决方案
- 使用共享依赖:
在构建配置中,确保共享依赖(如组件库)只被打包一次。可以通过配置 Webpack 的 externals 或使用 DllPlugin 来实现。
- 按需加载:
使用动态导入(import())来按需加载组件,避免在初始加载时将所有组件都打包。
- 优化构建配置:
检查构建工具的配置,确保没有不必要的重复打包。可以使用 Webpack 的 SplitChunksPlugin 来优化代码分割。
- 使用组件库:
如果使用了 UI 框架或组件库,确保使用按需加载的方式引入组件,避免引入整个库。
- 版本管理:
确保项目中使用的依赖版本一致,避免因版本不一致导致的重复打包。
开启GZip压缩
使用SSR
33、vue中给对象添加新属性界面不刷新
- 如果为对象添加少量的新属性,可以直接采用Vue.set()
- 如果需要为新对象添加大量的新属性,则通过Object.assign()创建新对象
- 如果你实在不知道怎么操作时,可采取$forceUpdate()进行强制刷新 (不建议)
34、Vue.observable
Vue.observable 是 Vue 2.x 中的一个方法,用于将一个普通的 JavaScript 对象转换为响应式对象。它会使这个对象的属性变得响应式,这意味着当对象的属性发生变化时,相关的视图会自动更新。
这个方法通常用于需要创建一个全局共享的响应式状态对象,或者是一个跨组件共享的状态管理机制,尽管在 Vue 3 中,Vue.observable 被 reactive 替代,但它在 Vue 2.x 项目中仍然很有用。
1 | // 创建一个普通对象 |
35、vue中的修饰符
- 表单修饰符
- lazy(在我们填完信息,光标离开标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息同步)
- trim
- number
- 事件修饰符
- stop
- prevent
- self
- once
- capture(使事件触发从包含这个元素的顶层开始往下触发)
- passive(在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符)
- native(让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件—使用.native修饰符来操作普通HTML标签是会令事件失效的)
- 鼠标按钮修饰符
- left 左键点击
- right 右键点击
- middle 中键点击
- 键盘修饰符
键盘修饰符是用来修饰键盘事件(onkeyup,onkeydown)的
还可以通过以下方式自定义一些全局的键盘码别名Vue.config.keyCodes.f2 = 113 - v-bind修饰符
- sync
- prop(设置自定义标签属性,避免暴露数据,防止污染HTML结构)
- camel(将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox)
36、vue自定义指令
注册一个自定义指令有全局注册与局部注册
全局注册主要是通过Vue.directive方法进行注册
1 | // 注册一个全局自定义指令 `v-focus` |
局部注册通过在组件options选项中设置directive属性
1 | directives: { |
自定义指令也像组件那样存在钩子函数:
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
- unbind:只调用一次,指令与元素解绑时调用
所有的钩子函数的参数都有以下:
- el:指令所绑定的元素,可以用来直接操作 DOM
- binding:一个对象,包含以下 property:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive=”1 + 1” 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive=”1 + 1” 中,表达式为 “1 + 1”。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
- vnode:Vue 编译生成的虚拟节点
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
37、vue3相对于vue2做的优化
| 优化点 | Vue 2 | Vue 3 |
|---|---|---|
| 响应式系统 | Object.defineProperty |
Proxy(性能更高) |
| Diff 算法(静态标记) | 逐个比对 | PatchFlag 跳过静态节点 |
| 静态提升 | 每次创建静态节点 | 只创建一次静态节点 |
| 事件监听缓存 | 事件重复绑定 | 事件缓存,避免重复绑定 |
| Fragment | 需要 div 包裹 |
组件支持多个根节点 |
| Teleport | 不能跨层级渲染 | 允许直接挂载到 body |
| Tree-shaking | 不能按需打包 | 代码按需加载,减少包体积 |
1. 编译阶段
- diff算法优化
vue3在diff算法中相比vue2增加了静态标记
关于这个静态标记,其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较 - 静态提升
Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用
这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用 - 事件监听缓存
默认情况下绑定事件行为会被视为动态绑定,所以每次都会去追踪它的变化
开启了缓存后,没有了静态标记。也就是说下次diff算法的时候直接使用 - SSR优化
当静态内容大到一定量级时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染
2. 源码体积
相比Vue2,Vue3整体体积变小了,除了移除一些不常用的API。再重要的是Tree shanking
任何一个函数,如ref、reavtived、computed等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小
3. 响应式系统
vue2中采用 defineProperty来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter和setter,实现响应式
vue3采用proxy重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历
- 可以监听动态属性的添加
- 可以监听到数组的索引和数组length属性
- 可以监听删除属性
38、Typescript数据类型
基本类型
- number(数字类型)
- string(字符串类型)
- boolean(布尔类型)
- null 和 undefined 类型
- object 对象类型
- array(数组类型)
- tuple(元组类型)
- enum(枚举类型)
- any(任意类型)
- void 类型
- never 类型
高级用法
- 交叉类型 &
- 联合类型 |
- 类型别名 type
- 类型索引 keyof
- 类型约束 extend
- 映射类型 in
- 条件类型 三元
39、this指向
- 全局函数:普通模式,普通函数中的this指向顶级对象window
严格模式,普通函数中的this指向undefined - 在对象的方法中,this指向的是该对象
- 在构造函数中,this指向构造函数的实例
- 在事件中,this指向事件源
40、call、apply、bind
- call
call方法的第一个参数也是this的指向,后面传入的是一个参数列表
跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次 - apply
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入
改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次 - bind
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)
改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
小结
- 三者都可以改变函数的
this对象指向 - 三者第一个参数都是
this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window - 三者都可以传参,但是
apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入 bind是返回绑定this之后的函数,apply、call则是立即执行
41、react生命周期函数
- 创建阶段
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
- 更新阶段
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidMount
- 卸载阶段
- componentWillUnmount
42、nextick使用场景和原理
使用场景:如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()
原理:
- 将回调函数放入 callbacks 队列:
当调用 Vue.nextTick() 时,传入的回调函数会被添加到一个内部的 callbacks 队列中,等待执行。
- 将执行函数放到微任务或宏任务中:
Vue 会使用 Promise 或 MutationObserver 等机制将这些回调函数安排在下一个事件循环的微任务队列中。这样可以确保在 DOM 更新完成后再执行这些回调。
- 事件循环执行回调:
当事件循环到达微任务或宏任务阶段时,Vue 会依次执行 callbacks 队列中的回调函数。此时,DOM 已经更新,回调函数可以安全地访问最新的 DOM 状态。
43、组件给name属性的优点
- 支持递归调用:
通过为组件设置 name 属性,可以实现组件的递归调用。组件可以在其自身内部引用 name,从而方便地创建嵌套结构。
- 便于调试:
name 属性为组件提供了一个具体的名称,这使得在调试时更容易识别和查找对应的组件。开发者可以在浏览器的开发者工具中快速定位到特定的组件实例。
- 组件缓存管理:
在使用 keep-alive 组件时,name 属性用于区分哪些组件需要被缓存,哪些不需要。通过 include 和 exclude 属性,可以指定要缓存的组件名称,从而优化性能和资源管理。
44、keep-alive实现原理
- 内部结构:
keep-alive 组件内部维护两个主要的数据结构:
key 数组:记录当前缓存的组件的 key 值。如果组件没有指定 key,keep-alive 会自动生成一个唯一的 key。
cache 对象:以 key 值为键,虚拟 DOM(vnode)为值,用于缓存组件的虚拟 DOM。
- 渲染逻辑:
在 keep-alive 的渲染函数中,首先检查当前渲染的 vnode 是否已经存在于缓存中。
如果存在,直接从缓存中读取对应的组件实例。
如果不存在,则将该组件的 vnode 缓存起来。
- 缓存管理:
当缓存的组件数量超过 max 设置的限制时,keep-alive 会移除 key 数组中的第一个元素,从而释放缓存空间。
45、长列表渲染
虚拟列表
46、 移动端兼容性
1、安卓浏览器看背景图片,有些设备会模糊
因为手机分辨率太小,如果按照分辨率来显示网页,字会非常小,安卓手机devicePixoRadio(像素比)比较乱,有1.5的,有2的也有3的。想让图片在手机里显示更为清晰,必须使用2x的背景图来代替img标签(一般情况下都是2倍的),或者指定background-size:contain;都可以
用-webkit-min-device-pixel-ratio可以做到不同倍数不同尺寸的图片:
1 | .icon-logo { |
2、防止手机中页面放大和缩小
1 | <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> |
3、上下拉动滚动条时卡顿、慢
1 | body { |
Android3+和iOSi5+支持CSS3的新属性为overflow-scrolling
4、长时间按住页面出现闪退
1 | element { |
5、iphone及ipad下输入框默认内阴影
1 | element { |
6、ios和android下触摸元素时出现半透明灰色遮罩
1 | element { |
设置alpha值为0就可以去除本透明灰色遮罩,备注:transparent的属性值在android下无效。
7、active兼容处理即伪类:active失效
方法一:body添加ontouchstart
1 | <body ontouchstart=''> |
方法二:js给document绑定touchstart或touchend事件
1 | <style> |
8、1px边框
在移动端中,如果给元素设置一个像素的边框的话,那么在手机上看起来是会比一个像素粗的。
解决方法:使用伪类元素模拟边框,使用transform缩放
1 | .button { |
9、webkit mask兼容处理
CSS3 的 mask 属性用于创建遮罩效果,允许开发者控制元素的可见性。通过使用遮罩,可以实现复杂的视觉效果,例如渐变、图形或图像遮罩,从而使元素的某些部分可见,而其他部分则被隐藏。
1 | .masked { |
某些低端手机不支持css3mask,可以选择性的进降级处理
比如可以使用js判断来引用不同class:
1 | if('WebkitMask' in documnet.documentElement.style) { |
10、pc端与移动端字体大小的问题
1 | html,body,form,fieldset,p,div,h1,h2,h3,h4,h5,h6 { |
pc端字体正常显示,但ios真机就出现,h1、span等标签字体比较大。
1 | html { |
11.transition闪屏
“Transition 闪屏” 是指在使用 CSS 过渡效果时,页面或元素出现短暂的闪烁或抖动现象。这种现象通常发生在使用 CSS 过渡属性(transition)时,尤其是在涉及 3D 转换(transform)的情况下。
1 | /*确保子元素在 3D 空间中正确呈现*/ |
12.圆角bug
某些Android手机圆角失效
1 | background-clip: padding-box; |
13.click的300ms延迟问题
在移动端中,click事件是生效的,但是,点击之后会有300ms的延迟响应
原因:safari是最早做出这个机制的,因为在移动端里,浏览器需要等待一段时间来判断此次用户操作是单击还是双击,所以就有click300ms的延迟机制,Android也很快就有了
不用click,用自定义事件tap
tap是需要自定义的:如果用户执行了touchstart在很短的时间又触发了touchend,且两次的距离很小,而且不能
引入fastclick库来解决
14.响应式图片
在移动端中,图片的处理应该是很谨慎的,假设有一张图片本身的尺寸是X宽,设置和包裹它的div一样宽,如果是div宽度小于图片宽度没有问题,但是如果div宽度大于图片的宽度,图片被拉伸失真
解决方法:让图片最大只能是自己的宽度
1 | img { |
15.点透bug的产生
例如:
1 | <div id="haorooms">点透事件测试</div> |
div是绝对定位的蒙层,并且z-index高于a。而a标签是页面中的一个链接,我们给div绑定tap事件:
1 | $('#haorooms').on('tap',function(){ |
我们点击蒙层时div正常消失,但是当我们在a标签上点击蒙层时,发现a链接被触发,这就是所谓的点透事件。
原因:
touchstart早于touchend早于click。即click的触发是由延迟的,这个时间大概在300ms左右,也就是说我们tap触发之后蒙层隐藏。此时click还没有触发,300ms之后由于蒙层隐藏,我们的click触发到了下面的a链接。
解决:
1.尽量都使用touch事件来替换click事件。例如用touchend事件(推荐)
2.用fastclick
3.用preventDefault阻止a标签的click
47、编程题(==事件循环==)
1 | var obj={ |
当 setTimeout 的回调函数执行时,i 的值已经是 this.skill.length,因此 this.skill[i] 会访问到 undefined,因为 i 超出了 skill 数组的索引范围。
setTimeout放在宏任务队列,this指向==window==,this.skill会发生报错
1 | var obj={ |
48、编程题(this指向和闭包)
1 | var num = 10; // f() 10 *2 *2 |
49、实现斐波那契的第N个值(从0开始),要求时间复杂度为O(n)
首先,说到斐波那契第一个想到的肯定是如下的算法,但这可是百度啊,如果只是这种程度的话如何能和同样面相同岗位的人竞争呢,所以我们得想到如下算法有什么缺点,然后如何优化
1 | function fib(n) { |
单纯的使用递归看似没什么问题,也能运算出结果,但是里面有个致命的问题,首先,时间复杂度就不对,递归思想的复杂度为 O(2^n) ,它不为O(n),然后还有会重复计算,比如计算n=3时,会计算fib(1) + fib(2),再次计算fib(4)时,会先算fib(3) = fib(1) + fib(2),然后再计算fib(4) = fib(1) + fib(2) + fib(3),在这里,fib(1)和fib(2)重复计算了两次,对于性能损耗极大。此时的你如果对动态规划敏感的话,就会从中想到动态规划其中最关键的特征——重叠子问题
因此,使用动态规划来规避重复计算问题,算是比较容易想到较优的一种解法,并且向面试官展现了你算法能力中有动态规划的思想,对于在面试中的你加分是极大的。
以下是动态规划思路的算法,状态转移方程为dp[i] = dp[i-1] + dp[i-2]
1 | function fibonacci(n) { |
50、手写EventBus
1 | class EventBus { |
使用上述 EventBus 类,你可以执行以下操作:
1 | // 创建全局事件总线对象 |
53、渲染帧
当首屏界面渲染完后,在应用的运行过程中,还会继续渲染,那么这个渲染过程是什么样的呢
首先,浏览器会定时刷新界面,不管js有没有改变dom。通常刷新频率和显示器帧率相同,大部分显示器帧率是60fps,≈16ms每帧。
如果渲染某一帧时候,DOM并未发生改变,就不需要启动主线程计算,如果DOM发生了变化,就可能触发重排或重绘,这时候就会启动主线程,重复上面提到的渲染过程。
在定时刷新帧的间隔,还会有其他的任务。比如用户可能会触发一些元素事件(输入文本点击按钮等),还可能有定时任务或者异步任务执行,这些任务的执行是会阻塞渲染的。
除了上面的任务,还有requestAnimationFrame和requestIdleCallback。在每一帧渲染完成后,在下一帧绘制之前如果有requestAnimationFrame,则调用之。如果在一帧渲染完后有空闲,就会执行requestIdleCallback。注册的回调什么是空闲时间呢,以60fps为例,如果浏览器渲染一帧发现不到16ms,那么剩余时间就算是空闲时间。
性能优化建议:
使用高效的动画方法:优先使用 CSS 动画或
requestAnimationFrame,以确保动画与浏览器刷新同步,减少卡顿。减少重排和重绘:避免频繁操作 DOM,尽量合并多次修改,降低布局和绘制的次数。
**合理使用
requestIdleCallback**:将非紧急任务放入requestIdleCallback,在浏览器空闲时执行,避免阻塞主线程。
54、垃圾回收机制
1、标记清除
这是 JavaScript 最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量, 垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
2、引用计数
在低版本 IE 中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加 1,如果该变量的值变成了另外一个,则这个值得引用次数减 1,当这个值的引用次数变为 0 的时 候,说明没有变量在使用,这个值没法被访问了,因此可以 将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的空间。在 IE 中虽然 JavaScript 对象通过标记清除的方式进行垃圾回收,但 BOM 与 DOM 对象却是通过引用计数回收垃圾的,也就是说只要涉及 BOM 及 DOM 就会出现循环引用。
55、Vue 的原理和内部实现
1、响应式系统
Vue 2 使用
Object.defineProperty进行数据劫持,Vue 3 则采用Proxy提高性能和深度监听能力。依赖收集和派发更新通过
Dep(Vue 2)或ReactiveEffect(Vue 3)实现。vue2
Vue 2 采用
Object.defineProperty进行数据劫持,并使用Dep(Dependency)类来管理依赖收集和更新。- 依赖收集
Dep维护一个subs数组(存储所有依赖该数据的Watcher)。- 当访问响应式数据时,当前的
Watcher(计算属性/组件渲染函数)被加入Dep。 Watcher通过Dep.target进行全局管理,每次访问数据时都会将当前Watcher记录到Dep.target,从而完成依赖收集。
- 派发更新
- 当数据发生变化时,
setter触发Dep.notify(),通知所有Watcher进行更新。Watcher会执行update(),重新计算视图或计算属性。
vue3
Vue 3 采用
Proxy进行响应式代理,并使用ReactiveEffect代替Dep + Watcher进行依赖管理。- 依赖收集
- Vue 3 采用
WeakMap存储所有响应式对象的依赖关系。 - 每个对象的每个 key 都有一个
Set存储依赖它的ReactiveEffect。
- Vue 3 采用
- 派发更新
- 当数据变化时,查找
targetMap,找到key相关的ReactiveEffect并执行更新。
- 当数据变化时,查找
- 使用 Proxy 实现响应式
- Vue 3 通过
Proxy监听对象的get和set,分别用于 依赖收集(track) 和 派发更新(trigger)
- Vue 3 通过
- 依赖收集
2、虚拟 DOM 与 Diff 算法
Vue 使用虚拟 DOM(VNode)来描述 UI,
patch过程对比新旧 VNode 进行最小化更新。Diff 采用双指针算法(更高效)。
子节点复用 & 最长递增子序列优化,减少 DOM 操作。
子节点复用:如果新旧虚拟 DOM 结构相似,不会直接删除旧节点,而是尽可能复用已有 DOM 元素
最长递增子序列优化:是一种数学算法,Vue 3 在 Diff 过程中使用它来优化列表项顺序变化时的 DOM 操作,当列表顺序发生变化时,如果无脑重排,可能会导致大量不必要的 DOM 移动。LIS 找到最小移动次数的策略,最大限度复用原有 DOM
diff整体策略为:深度优先,同层比较- 比较的过程中,循环从两边向中间收拢
Vue 3 采用
Block Tree进一步优化 Diff 过程,提高性能。vue3在diff算法中相比vue2增加了静态标记关于这个静态标记,其作用是为了会发生变化的地方添加一个
flag标记,下次发生变化的时候直接找该地方进行比较
3、模板编译
Vue 模板编译过程包括解析(Parse)、转换(Transform)、代码生成(Codegen)。
在Vue中编译器会先对template进⾏解析,这⼀步称为parse,结束之后会得到⼀个JS对象,我们成为抽象语法树AST,然后是对AST进⾏深加⼯的转换过程,这⼀步成为transform,最后将前⾯得到的AST⽣成为JS代码,也就是render函数
Vue 3 采用编译时优化,如静态提升(HoistStatic)和缓存事件处理函数,提高渲染效率。
静态提升
Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
事件监听缓存
默认情况下绑定事件行为会被视为动态绑定,所以每次都会去追踪它的变化。开启了缓存后,没有了静态标记。也就是说下次
diff算法的时候直接使用
4、组件更新与调度
Vue 采用异步更新策略,
nextTick将 DOM 更新任务推入微任务队列。Vue 2 采用
nextTick()和 队列调度,但仍然存在以下问题:- Watcher 可能重复执行:依赖同一个响应式数据的多个
Watcher会被多次触发。 - 任务执行顺序不确定:不同优先级的任务可能混杂执行。
- 异步更新策略简单:Vue 2 使用
nextTick()进行异步更新,但没有更精细的控制。
- Watcher 可能重复执行:依赖同一个响应式数据的多个
Vue 3 使用
scheduler机制,优化flushJob执行顺序,避免不必要的渲染。Vue 3 采用 任务调度器(Scheduler) 进行更新调度,核心是:
- 收集任务,避免重复
- 任务不会直接执行,而是放入任务队列,确保同一个
effect只执行一次。
- 任务不会直接执行,而是放入任务队列,确保同一个
- 按优先级排序执行
- Vue 3 将任务分类:
- 组件更新(普通 effect)(
effect.run()) - watch 监听(watchEffect、watch)
- 用户
nextTick回调
- 组件更新(普通 effect)(
- 组件更新的 优先级最高,watch 监听次之,
nextTick最后。
- Vue 3 将任务分类:
flushJob统一批量执行- Vue 3 采用 微任务(Promise.then) 调度
flushJob(),确保所有任务在一个事件循环中完成,减少渲染次数。
- Vue 3 采用 微任务(Promise.then) 调度
- 收集任务,避免重复
5、生命周期与调度机制
Vue 提供完整的生命周期钩子,Vue 3 通过
onMounted、onUpdated等组合 API 提供更细粒度控制。Vue 3 采用
setup预执行,减少实例化时的性能开销。vue2
data()、computed、methods都要挂载到this,每个组件实例都要创建一个新的this作用域。this绑定增加了调用开销,特别是深层次组件。watcher监听data变化,每个watcher都会影响组件性能。
vue3
组件创建前,Vue 先运行
setup(),初始化响应式数据 (ref/reactive)。**无需挂载到
this**,数据直接暴露给模板(避免this绑定开销)。减少
watcher数量,Vue 3 采用effect,可以在setup()里批量监听多个数据,避免 Vue 2 的watcher过多问题。
6、Vue 组件化与 Diff 优化
- Vue 组件基于
VNode进行渲染,Vue 3 采用Fragment减少额外的 DOM 包裹。 keep-alive通过缓存组件实例减少重复创建,Teleport提供跨层级渲染能力。
7、Vue 3 新特性
Composition API提供更好的逻辑复用能力。Suspense处理异步组件加载,Reactivity API提供更灵活的响应式能力。
56、Android 和 H5(HTML5)页面之间的通信
JavaScript Interface(JavaScript 接口)
原理: Android 提供了一种机制,可以将 Java 对象暴露给 WebView 中的 JavaScript 代码。通过这种方式,JavaScript 可以调用 Android 的本地方法。
实现:
在 Android 中,使用
addJavascriptInterface方法将 Java 对象绑定到 WebView。在 H5 页面中,通过 JavaScript 调用这个对象的方法。
1 | // Android 代码 |
57、在 Android 中实现实时定位的原理
实现步骤
- 权限请求:
- 在 Android 6.0 及以上版本,应用需要在运行时请求定位权限,包括 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION。
- 使用 FusedLocationProviderClient:
- Android 提供了
FusedLocationProviderClient作为统一的定位服务接口,简化了 GPS 和网络定位的使用。 - 通过 FusedLocationProviderClient 可以获取设备的当前位置,并设置位置更新的频率和精度。
- 设置位置请求:
- 创建 LocationRequest 对象,设置位置更新的间隔、优先级(如高精度、低功耗)等参数。
- 监听位置更新:
- 使用 LocationCallback 来接收位置更新。每当设备位置发生变化时,onLocationResult 方法会被调用。
- 处理位置数据:
- 在 onLocationResult 中处理新的位置信息,可以更新用户界面或执行其他逻辑。
58、在Android中实现文件上传和下载
在 Android 6.0 及以上版本,确保在运行时请求存储权限
文件上传
使用 Intent 选择文件,并在选择后将文件的 URI 传递给 H5 页面
当用户通过 Intent 选择文件时,Android 会返回一个 URI,表示所选文件的位置。这个 URI 可以用于访问文件的内容,例如读取文件、上传文件等。
文件下载
- 创建下载请求:
- 使用 DownloadManager.Request 类创建一个下载请求对象,并设置下载的 URL。
- 设置下载请求的属性:
设置下载的标题和描述,以便用户在通知栏中看到。
设置下载的可见性,通常选择在下载完成时通知用户。
设置下载文件的保存路径。
- 获取 DownloadManager 实例:
- 通过 getSystemService 方法获取 DownloadManager 的实例。
- 开始下载:
- 使用 enqueue 方法将下载请求添加到下载队列中。
59、Vue 和 React 的 Diff 算法 & 组件更新机制解析
Vue 和 React 都使用 虚拟 DOM(Virtual DOM) 来提高性能,核心优化点主要在 Diff 算法 和 组件更新粒度。但它们的 Diff 机制有所不同,Vue 进行了更多针对性优化,React 则采用 递归 Diff。
1. Diff 算法对比
1.1 Vue 的 Diff 算法
Vue 3 采用了 “编译时优化 + 运行时 Diff” 的混合方案,主要优化点:
- PatchFlag 标记动态节点(只对动态部分 Diff)。
- 静态提升(静态节点只创建一次)。
- Block Tree 追踪动态节点,减少不必要的比较。
核心逻辑:
静态节点(不会变化)跳过 Diff。
动态节点(带
PatchFlag)只 Diff 变化的部分,减少遍历。Diff 采用双指针算法(更高效)。
子节点复用 & 最长递增子序列优化,减少 DOM 操作。
子节点复用:如果新旧虚拟 DOM 结构相似,不会直接删除旧节点,而是尽可能复用已有 DOM 元素
最长递增子序列优化:是一种数学算法,Vue 3 在 Diff 过程中使用它来优化列表项顺序变化时的 DOM 操作,当列表顺序发生变化时,如果无脑重排,可能会导致大量不必要的 DOM 移动。LIS 找到最小移动次数的策略,最大限度复用原有 DOM
1.2 React 的 Diff 算法
React 采用 **”逐层对比 + 递归 Diff”**,主要优化点:
- 同层比较:React 只会比较同一层级的节点,不跨层对比。
- Key 机制优化:如果
key发生变化,会认为该节点已被删除,并创建新的节点。 - 不能静态标记,但 Fiber 架构可以分片更新,避免长时间阻塞。
对比项 Vue Diff 算法 React Diff 算法 Diff 方式 模板编译+运行时 Diff 递归 Diff 静态标记 PatchFlag跳过静态节点无静态标记(每次遍历) Diff 算法 双指针 & 最长递增子序列 O(n^3) 逐层递归 子节点优化 Block Tree 只追踪动态部分 需要 key来优化组件更新 组件内部 按动态节点更新 默认整个组件重新渲染
2. 组件更新粒度 & 机制
2.2React 的组件更新机制
React 采用 Fiber 机制,将 UI 更新拆分为小任务,避免阻塞主线程。
React 的更新策略
- 组件默认整棵树重新渲染(需
memo优化)。 - 使用
useState()触发更新。 - React 需要
useMemo()、useCallback()避免不必要的更新。
| 对比项 | Vue 组件更新 | React 组件更新 |
|---|---|---|
| 更新触发 | 依赖追踪更新(精准) | 组件默认重新渲染 |
| 局部更新 | PatchFlag 精准更新 | 默认重新计算 JSX |
| 优化手段 | 响应式 Proxy,自动优化 | useMemo/useCallback 手动优化 |
| 批量更新 | nextTick() 处理多次更新 |
React Fiber 时间分片 |
60、冷启动(Cold Start)
- 定义:用户首次打开应用或小程序,或在长时间未使用后重新启动时发生的情况。
- 优化策略:
- 启动优化:减少
app.js体积,异步加载资源,使用分包加载。 - 接口优化:减少
onLaunch期间的 API 调用,尽量延迟非必要的请求。 - 预加载:利用
wx.loadSubPackage()提前加载关键资源。
- 启动优化:减少
61、热启动(Hot Start)
- 定义:应用已在后台运行,用户短时间内重新进入时的启动过程。
- 优化策略:
- 保持状态:使用
Storage、globalData、keep-alive保持应用状态,避免重复初始化。 - 快速恢复:合理利用
App.onShow和Page.onShow处理 UI 刷新。 - 减少渲染:仅更新必要的数据,避免全局刷新。
- 保持状态:使用
62、离线缓存方案
- 目的:减少网络请求,提高用户体验,即使在无网络环境下也能使用部分功能。
- 方案:
- 本地存储:使用
wx.setStorageSync、localStorage缓存用户数据和请求结果。 - 离线包:小程序支持
预下载分包和分包加载,H5 可使用service worker缓存静态资源。 - IndexedDB:在 H5 中存储结构化数据,提高访问速度。
- 本地存储:使用
63、增量更新
- 目的:减少更新流量,提高更新速度,避免用户频繁下载完整包。
- 方案:
- 小程序:
- 微信小程序支持 热更新,后台发布后用户重新打开即可生效。
- 使用
wx.getUpdateManager()监听并提示用户更新。 - 采用
分包加载只更新必要的部分。
- H5:
- 使用
service worker+Cache API实现静态资源缓存和增量更新。 - 结合 Webpack
contenthash确保文件变更后缓存自动更新。
- 使用
- 小程序:
64、HTTP 和 HTTPS 解析
1. HTTP(HyperText Transfer Protocol,超文本传输协议)
HTTP 是一种用于客户端(如浏览器)和服务器之间通信的协议,主要用于网页数据传输。
特点:
- 明文传输:数据未加密,容易被窃听和篡改(如中间人攻击)。
- 无状态:每次请求都是独立的,服务器不会主动保留先前的请求信息(可通过 Cookie、Session 解决)。
- 默认端口:80。
2. HTTPS(HyperText Transfer Protocol Secure,安全超文本传输协议)
HTTPS 是 HTTP + SSL/TLS 加密协议的组合,旨在提供数据加密、安全认证和完整性。
特点:
- 数据加密:使用 SSL/TLS 进行加密,防止数据被窃听。
- 数据完整性:防止数据在传输过程中被篡改(如 MITM 攻击)。
- 身份验证:通过 SSL 证书验证服务器身份,防止钓鱼网站欺骗用户。
- 默认端口:443。
工作流程:
- SSL/TLS 握手:
- 客户端请求 HTTPS 服务器,服务器返回 SSL 证书。
- 客户端验证证书合法性,并与服务器协商加密算法。
- 双方生成对称密钥,完成安全通信的准备。
- 加密通信:
- 客户端使用对称加密密钥加密数据并发送到服务器。
- 服务器解密数据,处理请求后加密响应数据返回给客户端。
- 客户端解密数据并渲染。
为什么要使用 HTTPS?
- 保护用户隐私(防止流量劫持、钓鱼攻击)。
- 提高网站可信度(浏览器会标记 HTTP 为“不安全”)。
- 有利于 SEO(谷歌、百度等搜索引擎优先收录 HTTPS 网站)。
- 支持 HTTP/2(提高数据传输效率)
SSL 和 TLS 的主要区别
| 对比项 | SSL | TLS |
|---|---|---|
| 安全性 | 存在已知漏洞(POODLE、BEAST) | 更安全,移除了不安全算法 |
| 握手方式 | 需要多次交互,效率较低 | 简化握手流程,提高性能 |
| 加密算法 | 依赖 RC4、MD5、SHA-1(已不安全) | 支持 AES、ChaCha20、SHA-256 等 |
| 支持状态 | SSL 3.0 及以下已废弃 | TLS 1.2 及以上广泛使用,TLS 1.3 最高效 |
| 协议层级 | 应用层协议 | 传输层协议 |
| 是否兼容 SSL | 仅支持 SSL | 兼容部分 SSL 功能,可回退(但不推荐) |
65、Nginx 的作用
静态资源服务器
反向代理
负载均衡
API 网关
Web 服务器(如替代 Apache)
限流、防火墙等
66、前端工程化
前端工程化是一套针对前端开发流程的系统化、标准化实践,旨在提高开发效率、代码质量和可维护性。它涵盖了开发、构建、测试、部署等多个环节,主要包括以下几个方面:
1. 模块化
目的:提升代码复用性和维护性。
2. 组件化
目的:封装 UI 组件,提升复用性,降低耦合度。
3. 规范化
目的:统一代码风格,减少维护成本。
方式:
- 代码规范:ESLint、Prettier
- Git 规范:Commitizen、husky + lint-staged
- 项目结构:合理划分目录、使用 alias(如 Vite
resolve.alias)
4. 自动化
目的:减少人为错误,提高开发效率。
方式:
- 代码检查:ESLint、StyleLint、TypeScript
- 单元测试:Jest、Vitest、Mocha + Chai
- 集成测试:Cypress、Playwright
- 持续集成(CI/CD):GitHub Actions、Jenkins
5. 构建优化
目的:提升打包性能,减少体积,提高加载速度。
方式:
- 构建工具:Vite、Webpack、Rollup、esbuild
- 代码拆分:动态导入(
import())、按需加载 - Tree Shaking:移除未使用代码(Webpack、Rollup)
- Gzip/压缩:vite-plugin-compression
- PWA:VitePWA
6. 性能优化
目的:提升页面加载速度、减少白屏时间。
方式:
- 资源优化:图片压缩(vite-plugin-imagemin)、SVG Sprites
- 缓存优化:Service Worker、LocalStorage、IndexedDB
- CDN:静态资源分发(Aliyun OSS、腾讯云 COS)
- SSR/SSG:Nuxt.js(Vue)、Next.js(React)
7. 部署与运维
目的:自动化发布,提升稳定性。
方式:
- Docker 容器化:Nginx 部署前端资源
- CI/CD:GitHub Actions、Jenkins、GitLab CI
- 灰度发布:阿里云 EDAS、腾讯云 TKE
67、自定义 Webpack Loader、Plugin 或 Vite Plugin 来解决以下实际业务问题:
Webpack Loader 相关场景
Markdown 转 Vue 组件
1
2
3
4
5
6const markdown = require('markdown-it')();
module.exports = function (source) {
const html = markdown.render(source);
return `<template><div>${html}</div></template>`;
};1
2
3
4
5
6
7
8module: {
rules: [
{
test: /\.md$/,
use: path.resolve(__dirname, './markdown-loader.js')
}
]
}SVG 变 Vue 组件
在 Vue 项目中使用 SVG 作为组件,而不是 img 标签,方便控制颜色和动画
自动注入环境变量
场景:需要在代码里插入某些环境变量,例如
__BUILD_TIME__,方便调试。
解决方案:自定义 Loader,在 JavaScript 代码中替换变量。
Webpack Plugin 相关场景
生成 Sitemap
场景:你有一个前端应用,需要自动生成 sitemap.xml 以提高 SEO。
解决方案:自定义 Plugin,在 Webpack 编译完成后,自动遍历路由并生成sitemap.xml。1
2
3
4
5
6
7
8
9
10
11
12
13class SitemapPlugin {
apply(compiler) {
compiler.hooks.done.tap('SitemapPlugin', stats => {
const routes = ['/home', '/about', '/contact'];
const sitemap = routes.map(r => `<url><loc>https://example.com${r}</loc></url>`).join('');
require('fs').writeFileSync('dist/sitemap.xml', `<urlset>${sitemap}</urlset>`);
});
}
}
module.exports = {
plugins: [new SitemapPlugin()]
};构建完成后自动上传 CDN
场景:项目打包后,需要把
dist目录的资源自动上传到阿里云 OSS 或腾讯云 COS。
解决方案:编写 Plugin,在done钩子里上传文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class UploadPlugin {
apply(compiler) {
compiler.hooks.done.tapAsync('UploadPlugin', (stats, callback) => {
const ossClient = require('ali-oss')({ region: 'oss-cn-hangzhou' });
ossClient.put('path/to/cdn', 'dist/index.js').then(() => {
console.log('文件上传完成');
callback();
});
});
}
}
module.exports = {
plugins: [new UploadPlugin()]
};移除无用的 CSS 代码
场景:打包后的 CSS 文件中可能包含未使用的样式,需要自动移除无用的 CSS。
解决方案:使用 PurgeCSS Plugin,或者自己编写一个简单的 Webpack Plugin,基于 AST 分析并删除无用的 CSS。
Vite Plugin 相关场景
自动 Mock 接口
场景:本地开发时,前端需要 Mock 后端接口,避免 CORS 问题。
解决方案:自定义 Vite Plugin,在configureServer中拦截 API 请求,返回 Mock 数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14export default function mockPlugin() {
return {
name: 'vite-plugin-mock',
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url === '/api/user') {
res.end(JSON.stringify({ id: 1, name: '张三' }));
} else {
next();
}
});
}
};
}图片自动转换 WebP
场景:前端图片较多,想要自动转换为 WebP 格式以减少加载时间。
解决方案:监听静态资源,自动转换 PNG/JPG 为 WebP。1
2
3
4
5
6
7
8
9
10
11
12
13
14import imagemin from 'imagemin';
import imageminWebp from 'imagemin-webp';
export default function webpPlugin() {
return {
name: 'vite-plugin-webp',
async transform(code, id) {
if (id.endsWith('.png') || id.endsWith('.jpg')) {
const buffer = await imagemin.buffer(Buffer.from(code), { plugins: [imageminWebp()] });
return buffer.toString('base64');
}
}
};
}HMR 支持 JSON 配置
场景:你的项目里有
config.json,但默认 Vite 只支持.js代码热更新。
解决方案:创建一个 Plugin,监听 JSON 变化并触发 HMR。1
2
3
4
5
6
7
8
9
10export default function jsonHmrPlugin() {
return {
name: 'vite-plugin-json-hmr',
handleHotUpdate({ file, server }) {
if (file.endsWith('.json')) {
server.ws.send({ type: 'full-reload' });
}
}
};
}
68、Node.js 是什么?它的主要特点是什么?
- Node.js 是一个基于 V8 引擎的 JavaScript 运行时,适用于构建高性能、非阻塞 I/O 的服务器端应用。
- 主要特点包括:单线程、事件驱动、非阻塞 I/O、模块化(CommonJS/ESM)。
Node.js 中如何处理异步操作?
主要方式有:
- 回调函数(callback)
- Promise
- async/await
- 事件驱动(EventEmitter)
Node.js 可制作的工具
| 工具类型 | 用途 | 示例 |
|---|---|---|
| CLI 工具 | 命令行工具 | npm、vue-cli |
| 爬虫 | 网页数据抓取 | puppeteer |
| Web 服务器 | 提供 API | Express |
| 静态资源服务器 | 共享 HTML/CSS | http-server |
| 文件处理 | 读写 JSON、CSV | fs |
| 代理服务器 | API 转发 | http-proxy |
| 定时任务 | 自动执行脚本 | node-schedule |
| 格式化工具 | 代码格式化 | prettier |
| HTTP 客户端 | API 调试 | axios |
| JSON 数据库 | 轻量级数据库 | lowdb |
中间件的主要作用
| 作用 | 说明 | 示例 |
|---|---|---|
| 请求解析 | 解析请求数据(JSON、表单、文件) | express.json() 解析 JSON |
| 身份验证 | 校验用户登录状态(JWT、Session) | passport.js |
| 日志记录 | 记录访问日志,方便调试 | morgan |
| 错误处理 | 统一捕获错误,返回友好的提示 | express-error-handler |
| CORS 处理 | 允许跨域访问,解决跨域问题 | cors |
| 代理转发 | 将请求转发到其他服务器 | http-proxy-middleware |
| 限流防护 | 保护服务器不被攻击(DDOS 防护) | express-rate-limit |
69、Monorepo 简介及其与包管理工具(npm、yarn、pnpm)之间的关系
Monorepo模式:Monorepo 是一种项目开发与管理的策略模式,它代表”单一代码仓库”(Monolithic Repository)。在 Monorepo 模式中,所有相关的项目和组件都被存储在一个统一的代码仓库中,而不是分散在多个独立的代码仓库中,这些项目之间还可能会有依赖关系。
轻量化 Monorepo 方案
Lerna 是什么?
- Lerna 是 Babel 为实现 Monorepo 开发的工具;最擅长管理依赖关系和发布
- Lerna 优化了多包工作流,解决了多包依赖、发版手动维护版本等问题
- Lerna 不提供构建、测试等任务,工程能力较弱,项目中往往需要基于它进行顶层能力的封装
Lerna 主要做三件事
- 为单个包或多个包运行命令 (lerna run)
- 管理依赖项 (lerna bootstrap)
- 发布依赖包,处理版本管理,并生成变更日志 (lerna publish)
Lerna 能解决了什么问题?
- 代码共享,调试便捷: 一个依赖包更新,其他依赖此包的包/项目无需安装最新版本,因为 Lerna 自动 Link
- 安装依赖,减少冗余:多个包都使用相同版本的依赖包时,Lerna 优先将依赖包安装在根目录
- 规范版本管理: Lerna 通过 Git 检测代码变动,自动发版、更新版本号;两种模式管理多个依赖包的版本号
- 自动生成发版日志:使用插件,根据 Git Commit 记录,自动生成 ChangeLog
Lerna 自动检测发布,判断逻辑
- 校验本地是否有没有被
commit内容? - 判断当前的分支是否正常?
- 判断当前分支是否在
remote存在? - 判断当前分支是否在
lerna.json允许的allowBranch设置之中? - 判断当前分支提交是否落后于 remote
Lerna 工作模式
Lerna 允许您使用两种模式来管理您的项目:固定模式(Fixed)、独立模式(Independent)
① 固定模式(Locked mode)
- Lerna 把多个软件包当做一个整体工程,每次发布所有软件包版本号统一升级(版本一致),无论是否修改
- 项目初始化时,
lerna init默认是 Locked mode
1 | { |
② 独立模式(Independent mode)
- Lerna 单独管理每个软件包的版本号,每次执行发布指令,Git 检查文件变动,只发版升级有调整的软件包
- 项目初始化时,
lerna init --independent
Monorepo 适用于 Node.js 微服务的场景
1. 共享代码和依赖
- 避免重复代码:多个微服务可以直接引用相同的 工具库、日志库、数据库封装模块 等,而无需发布到 npm 再安装。
- 统一依赖版本:所有微服务共用
package.json,避免因依赖版本不同导致不兼容问题。
2. 统一 CI/CD 和版本管理
- 集中管理代码,提高协作效率。
- 增量构建:只构建变更的服务,提高 CI/CD 速度(如
turborepo、Nx支持智能构建)。 - 批量发布:可以通过
lerna、changesets等工具一次性发布多个微服务。
3. 适合多团队协作
- 不同团队可以管理不同的微服务,但仍然在同一个仓库内,便于协作。
- 代码变更可以同步进行,减少跨仓库 Pull Request 的麻烦。
70、面向对象、函数式编程范式
在JavaScript中,你可以使用面向对象编程(OOP)和函数式编程(FP)范式来实现不同的编程风格和模式。这两种范式各有特点,适用于不同的场景和问题。
面向对象编程(OOP)
面向对象编程是一种基于对象和类的编程范式。在JavaScript中,你可以使用构造函数、原型(prototype)和类(ECMAScript 6引入)来实现OOP。
示例:使用构造函数
1 | function Person(name, age) { |
示例:使用ES6类
1 | class Person { |
函数式编程(FP)
特点:
- 函数是一等公民:函数可以作为参数传递和返回。
- 纯函数:输入相同则输出相同,且没有副作用。
- 不可变性:数据一旦创建,不可更改。
- 递归代替循环:使用递归来实现循环逻辑。
在JavaScript中,你可以通过使用高阶函数、闭包和纯函数来实现FP。
示例:使用高阶函数和纯函数
1 | const add = (x, y) => x + y; |
结合使用OOP和FP范式
在实际的项目开发中,你可能需要结合使用OOP和FP范式。例如,你可以使用面向对象来组织和管理数据和状态,同时使用函数式编程来处理逻辑和数据转换。
示例:结合使用OOP和FP
1 | class MathOperations { |
在这个例子中,MathOperations 类封装了加法逻辑,而 addAndLog 是一个纯函数,它不修改状态并依赖于类的方法。这种方式结合了面向对象的数据封装能力和函数式的纯净数据处理方式。
71、单元测试、TDD 等开发模式
🔹单元测试是指对最小的可测试单元(通常是函数或类的方法)进行独立测试,以确保它的行为符合预期。
✅ 为什么要写单元测试?
- 防止回归(Regression):修改代码后,能够快速检查是否影响了已有功能。
- 提高代码质量:测试驱动良好设计,避免意外 bug。
- 减少 Debug 时间:问题能尽早发现,而不是上线后才修复。
- 提升可维护性:当代码库变大时,测试确保旧代码不会因新改动而崩溃。
🔹TDD(Test-Driven Development,测试驱动开发)是一种开发模式,它的核心理念是:
先写测试,再写代码
TDD 的基本流程(红 - 绿 - 重构):
- 红(Red):先写一个失败的测试(因为代码还未实现)。
- 绿(Green):编写最简单的代码,使测试通过。
- 重构(Refactor):优化代码,使其更清晰、可维护,同时保持测试通过。
💡 TDD 适用于业务逻辑复杂、长期维护的项目,而短期的 MVP 项目可能不需要严格执行 TDD。
🔹 5. 单元测试 vs 其他测试
| 测试类型 | 作用 | 示例 | 适用场景 |
|---|---|---|---|
| 单元测试(Unit Test) | 测试最小的代码单元(函数/类) | jest, mocha |
业务逻辑、工具函数 |
| 集成测试(Integration Test) | 测试多个模块或 API 之间的交互 | supertest, jest |
后端 API、数据库交互 |
| 端到端测试(E2E Test) | 模拟用户操作,测试整个系统 | Cypress, Playwright |
Web 前端、自动化测试 |
| UI 测试 | 测试前端组件 | Jest + Vue Test Utils, React Testing Library |
Vue |
💡 单元测试 + TDD 可用于核心业务逻辑,E2E 测试可用于用户流程验证。
🔹 6. 如何在团队中推广单元测试和 TDD?
- 从关键业务代码开始,优先测试复杂的计算逻辑、API 服务等。
- 引入 Jest / Vitest,降低前端 Vue/React 项目写测试的成本。
- 设定测试覆盖率(Code Coverage)目标,比如 80% 以上。
- 在 CI/CD 流程中自动运行测试,避免手动检查。
- 团队内部培训,让成员熟悉 TDD 和单元测试的好处。
🚀 总结
- 单元测试(Unit Testing):针对单个函数/类,防止 bug 复现,提高代码质量。
- TDD(测试驱动开发):先写测试,再写代码,遵循红-绿-重构循环,提高代码可测试性。
- BDD(行为驱动开发):关注用户行为,用自然语言描述测试。
- 单元测试 ≠ 全部测试,应结合 集成测试、E2E 测试 进行全面质量保障。
- 工具推荐:前端(Jest, Vitest, Cypress)、后端(Mocha, Supertest)、UI(Vue Test Utils, React Testing Library)。