面试八股
HTTP状态码及常见状态码
HTTP状态码
• 1xx:指示信息类,表示请求已接受,继续处理
• 2xx:指示成功类,表示请求已成功接受
• 3xx:指示重定向,表示要完成请求必须进行更近一步的操作
• 4xx:指示客户端错误,请求有语法错误或请求无法实现
• 5xx:指示服务器错误,服务器未能实现合法的请求
常见状态码
• 200 OK:客户端请求成功
• 301 Moved Permanently:所请求的页面已经永久重定向至新的URL
• 302 Found:所请求的页面已经临时重定向至新的URL
• 304 Not Modified 未修改
• 403 Forbidden:对请求页面的访问被禁止
• 404 Not Found:请求资源不存在
• 500 Internal Server Error:服务器发生不可预期的错误原来缓冲的文档还可以继续使用
• 503 Server Unavailable:请求未完成,服务器临时过载或宕机,一段时间后可恢复正常
HTTP报文的组成成分
请求报文{ 请求行、请求头、空行、请求体 }
响应报文{ 状态行、响应头、空行、响应体 }
Request Header:
GET /sample.Jsp HTTP/1.1 //请求行:{http方法、页面地址、http协议、http版本}
Host://请求的目标域名和端口号
Origin: //请求的来源域名和端口号 (跨域请求时,浏览器会自动带上这个头信息)
Referer: //请求资源的完整URI
User-Agent: //浏览器信息
Cookie: 当前域名下的Cookie
Accept: //代表客户端希望接受的数据类型
Accept-Encoding: //代表客户端能支持的压缩格式
Accept-Language://代表客户端可以支持的语言
Connection: keep-alive //告诉服务器,客户端需要的tcp连接是一个长连接
Response Header:
HTTP/1.1 200 OK // 响应状态行:{http协议、http版本 响应码 状态}
Date: //服务端发送资源时的服务器时间
Expires: //比较过时的一种验证缓存的方式,与浏览器(客户端)的时间比较,超过这个时间就不用缓存(不和服务器进行验证),适合版本比较稳定的网页
Cache-Control: // 控制缓存的方式
etag: // 一般是Nginx静态服务器发来的静态文件签名,浏览在没有“Disabled cache”情况下,接收到etag后,同一个url第二次请求就会自动带上“If-None-Match”
Last-Modified://是服务器发来的当前资源最后一次修改的时间,下次请求时,如果服务器上当前资源的修改时间大于这个时间,就返回新的资源内容
Content-Type: text/html; charset=utf-8 //如果返回是流式的数据,我们就必须告诉浏览器这个头,不然浏览器会下载这个页面,同时告诉浏览器是utf8编码,否则可能出现乱码
Content-Encoding: gzip //告诉客户端,应该采用gzip对资源进行解码
Connection: keep-alive //告诉客户端服务器的tcp连接也是一个长连接
HTTP请求/响应的步骤
- 用户在浏览器地址栏输入URL,URL由协议、域名、端口号、文件路径组成,没有文件路径的时候,就会访问根目录下的默认文件,例如index.html
- 浏览器检查本地缓存是否有该域名对应的IP地址。如果缓存中没有,浏览器会通过递归方式请求本地DNS服务器、本地DNS服务器通过迭代方式请求根域名服务器、顶级域名服务器、权威域名服务器等,最终将IP地址返回给客户端,这个过程中浏览器、操作系统、各级DNS服务器都会先查看本地缓存,没有记录再去查询
- 浏览器通过IP地址和端口号与服务器建立TCP连接。也就是”三次握手“过程,如果是HTTPS协议,还会进行TLS/SSL握手,确保通信安全。
- 浏览器向服务器发送HTTP请求,请求头中包含浏览器信息、支持的编码格式、Cookie等。服务器接收到请求后,根据路径和参数处理请求,生成响应内容。
- 服务器将响应数据和HTTP状态码返回给浏览器。响应头中包含内容类型、缓存策略等信息。
- 览器接收到响应后,开始解析HTML文件。解析过程中,浏览器会加载HTML中的CSS、JavaScript、图片等资源。浏览器构建DOM树、CSSOM树,合并生成渲染树,最后进行布局和绘制,将页面显示在屏幕上。
- 当所有资源加载完毕,页面完全渲染后,浏览器触发load事件,页面加载完成。
- TCP连接关闭(四次挥手)
POST请求和GET请求
- GET传递的参数在url后拼接,不安全。POST传递的参数在request body中,更安全。
- GET对数据进行查询,POST主要对数据进行增删改!
- GET在浏览器回退时是无害的,而POST会再次提交请求
- GET请求会被浏览器主动缓存,POST不会,要手动设置
- GET请求长度有限制,POST没有
cookie、localStorage、sessionStorage区别
- 相同:在本地(浏览器端)存储数据
- 不同:
- cookie同域名共享;localStorage要在相同的协议、域名、端口号下;sessionStorage比localStorage更严苛一点,除了协议、主机名、端口外,还要求在同一窗口(也就是浏览器的标签页)下。
- localStorage是永久存储,除非手动删除;sessionStorage当会话结束(当前页面关闭的时候,自动销毁);cookie可以设置过期的时间,默认是会话结束的时候,当时间到期自动销毁
- cookie的数据会在每一次发送http请求的时候,同时发送给服务器而localStorage、sessionStorage不会。
- sessionStorage和localStorage的存储比cookie大得多
当Token被窃取并被用于伪造请求
快速撤销被盗Token。
监控异常请求:检查日志,识别异常IP、高频请求或非常规操作,及时拦截。
设置较短的过期时间。
Token与首次请求的IP或设备指纹绑定,异常时拒绝请求。
强制使用HTTPS,避免明文传输。避免将Token存储在
localStorage中,优先使用HttpOnly+Secure的Cookie。避免在URL、日志或前端代码中暴露Token。对敏感操作(如修改密码)要求二次认证(如短信/OTP)。
Token使用限制
- 单次Token(如微信支付的
nonce_str)或限制Token使用次数。 - 动态Token(如每次请求生成新Token,旧Token立即失效)。
- 单次Token(如微信支付的
OAuth2.0防护
XSS攻击
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。
根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种。
对比:
| 类型 | 存储区 | 插入点 |
|---|---|---|
| 存储型 XSS | 后端数据库 | HTML |
| 反射型 XSS | URL | HTML |
| DOM 型 XSS | 后端数据库/前端存储/URL | 前端 JavaScript |
常用防范方法
- httpOnly: 在 cookie 中设置 HttpOnly 属性后,js脚本将无法读取到 cookie 信息。
- 输入过滤: 一般是用于对于输入格式的检查,例如:邮箱,电话号码,用户名,密码……等,按照规定的格式输入。
- 转义 HTML: 如果拼接 HTML 是必要的,就需要对于引号,尖括号,斜杠进行转义。
预防 DOM 型 XSS 攻击
DOM 型 XSS 攻击,实际上就是网站前端 JavaScript代码本身不够严谨,把不可信的数据当作代码执行了。
在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等。
如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患。
DOM 中的内联事件监听器,如 location、onclick、onerror、onload、onmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患。
CSRF 跨站点请求伪造
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。如:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
XSS 是代码注入问题,CSRF 是 HTTP 问题。 XSS 是内容没有过滤导致浏览器将攻击者的输入当代码执行。CSRF 则是因为浏览器在发送 HTTP 请求时候自动带上 cookie,而一般网站的 session 都存在 cookie里面。
防御
- 验证码;强制用户必须与应用进行交互,才能完成最终请求。此种方式能很好的遏制 csrf,但是用户体验比较差。
- Referer check;请求来源限制,此种方法成本最低,但是并不能保证 100% 有效,因为服务器并不是什么时候都能取到 Referer,而且低版本的浏览器存在伪造 Referer 的风险。
- token;token 验证的 CSRF 防御机制是公认最合适的方案。
进程和线程
进程是是系统进行资源分配的基本单位,线程是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源,每个线程拥有独立的栈和寄存器。进程之间相互独立,每个进程拥有自己的地址空间和系统资源,多个线程共享所属进程的资源。线程不拥有自己独立的地址空间,所以切换较快。进程之间有进程间通信机制,如共享存储、消息队列、管道等。线程可以直接访问同一进程的全局变量,通信成本较低。创建和切换进程的开销更大。一个进程崩溃不会影响其他进程,而一个线程崩溃,可能会导致整个进程崩溃。
面向对象和面向过程有什么区别,JavaScript是哪一种
面向对象以对象为基本单元,数据和方法绑定在对象中。强调封装继承多态。
面向过程以函数为基本单元,数据和方法分离,通过函数和流程控制组织代码。
JavaScript属于面向对象的语言,两种范式都支持。
什么是模块化设计
将代码拆分为独立的、功能单一的模块,每个模块负责特定功能,通过明确的接口与其他模块通信。减少代码耦合,提高可维护性和复用性。
两种规范
- CommonJS Node.js默认
- ES Modules 原生浏览器支持
Vue和React有什么区别,有什么相同
不同:
- 模板语法:React采用JSX,Vue采用基于HTML的模板语法
- 数据绑定:Vue实现了双向的数据绑定,React需要手动控制组件的属性和状态
- 状态管理:Vue采用Vuex/Pinia,React使用Redux
- 组件通信不同:Vue采用props和事件来实现父子组件通信,React采用props和回调函数实现
- 生命周期不同:Vue有8个生命周期钩子,React有10个
- 响应式原理不同:Vue使用双向数据绑定来实现数据更新,React使用单向数据流来实现
相同:
Vue和React都采用了组件化开发的方式。Vue和React都使用虚拟 DOM 技术。Vue和React都支持响应式更新。Vue和React都具有良好的集成能力。
生命周期函数
组件从创建到销毁的一些钩子函数
vue3
React
constructor() useState()
render() 函数本身
componentDidMount() useEffect()
getDerivedStateFromProps() useState()返回的setState()函数
shouldComponentUpdate() useMemo()
getSnapshotBeforeUpdate()
componentDidUpdate() useEffect()
componentWillUnmount() useEffect()的返回函数
闭包
- 函数可以捕获外部作用域的变量,即使在定义的外部作用域之外被调用。
- 在函数外部无法读取函数内的局部变量。出于种种原因,我们有时候需要得到函数内的局部变量。那就是在函数的内部,再定义一个函数,利用内层函数来获取外层函数的局部变量。常用于模块化编程和数据封装。
- 闭包可能会引起内存泄漏和性能问题
防抖节流
防抖和节流都是前端限制函数触发频率的技术。
防抖:函数触发后的一段时间内,不再触发事件时,才执行一次。应用:搜索栏搜索历史、浏览器窗口大小调整引起的页面变化
节流:函数触发后的一段时间内,保证只触发这一次。应用:无限滚动列表加载数据、表单提交。
箭头函数和普通函数的区别
- 普通函数有自己的this,由调用方式决定。对象调用指向调用,new调用指向实例;箭头函数没有自己的this,继承外部作用域的this
- 箭头函数不能作为构造函数,普通函数可以
- 箭头函数有简洁语法,函数体只有一个表达式的时候,可以省略大括号和return
- 普通函数可以访问隐式的arguments对象,它包含传递给函数的所有参数,而箭头函数没有
webpack构建过程
- 初始化配置项 config.js
- 解析依赖:模块之间的依赖关系
- 模块转化 :vue文件,jsx文件等解析为对应和html、css、js文件
- 优化:消除未使用的代码块、代码分割、代码压缩。
- 输出:将优化后的代码输出到指定目录中
Vite 之所以在启动和热更新快的原因
- Vite 直接基于浏览器的原生 ESM 功能,无需打包启动,在浏览器请求时按需编译。
- 启动时使用 esbuild将第三方依赖预编译为 ESM 格式并缓存,后续启动直接使用缓存。
- 更新时仅替换修改的模块及其依赖链,无需重建整个 bundle。
Vite生产环境使用rollup
rollup是js模块打包工具,将分散的ESM代码高效打包为少数几个优化后的文件。它的核心优势就是使用Tree-shaking进行优化:
- 从入口文件出发,静态分析所有 import/export 语句,构建依赖关系图。
- 遍历依赖图,标记哪些
export被其他模块import并使用。 - 将标记为未使用的代码从最终 Bundle 中移除。生成扁平化的 Bundle(合并作用域、变量名压缩等)。
跨域
跨域是指在浏览器中,当一个网页的脚本试图获取另一个源的资源的时候,由于同源策略限制协议域名端口号必须相同,可能会被浏览器阻止。解决方法CORS,JSONP,开发时正向代理(dev-server)、部署时反向代理(nginx)、WebSocket
正向代理和反向代理
正向代理代理客户端,目标服务器只能看到代理的IP,无法知道真实客户端的身份,可以用来做科学上网、爬虫、缓存加速。
反向代理代理服务器端,客户端以为反向代理就是真实的服务器,不知道后端服务器的存在。可以用来做CDN、负载均衡、隐藏服务器架构
强缓存和协商缓存
强缓存:不会向服务器发送请求,直接从缓存中读取资源。
协商缓存:向服务器发送请求,服务器会根据携带的request header来判断是否命中协商缓存。如果命中,浏览器返回304并带上新的response header通知浏览器从缓存中读取资源
两者的共同点是,都是从客户端缓存中读取资源;区别是强缓存不会发请求,协商缓存会发请求
浏览器的缓存过程
- 浏览器第一次加载资源,服务器返回200,浏览器将资源从服务器上下载下来,并把
response header以及该请求的返回时间一并缓存 - 下一次加载资源时,比较当前请求时间和上次请求时间有没有超过
cache-control设置的max-age,如果没有超过则命中强缓存,直接从本地读取文件。如果时间过期,向服务器发送header带有If-None-Match和If-Modified-Since的请求。 - 服务器接到请求后,又出现根据
Etag的值判断文件有没有做修改,一致则命中协商缓存,返回304。如果值不一样,直接返回新的资源。 - 如果收到的请求没有
Etag值,则将If-Modified-Since和被修改的文件时间作对比
重排和重绘
重排:DOM结构发生改变以及元素的几何属性发生变化时,重新计算元素的位置和尺寸
重绘:元素的外观发生改变但不影响布局的时候,重新绘制元素
重排的渲染消耗更大,频繁的重排和重绘都会影响页面性能。
UDP和TCP的区别、使用场景、如何让UDP变得可靠
UDP和TCP都是计算机网络传输层的协议,UDP是无连接、不可靠的,TCP是有链接、可靠的。UDP适合数据量小、需要实时传输、丢失数据可容忍的场景,如音视频传输、在线游戏。TCP适合于需要数据完整性和顺序性的场景,如网页浏览、文件传输。可以在应用层实现重传机制、数据校验来保证UDP的可靠性。
前端路由的几种方式
hash路由:利用URL中#后的哈希值变化不会引起路由跳转的特性。通过监听hashChange事件,读取hash值来实现。兼容性更好。 (最初是用来做锚点定位,滚动到页面的指定位置的)
history路由:利用history API修改URL路径并通过监听popState事件来监听浏览器前进后退。URL更简洁,使得服务器端可以解析完整路径
Vue的响应式原理
vue2中是通过Object.defineProperty来实现的,在页面加载时,vue会使用Object.defineProperty来重新定义data中的所有属性,劫持各个属性的setter和getter,读取属性时触发getter方法,修改属性时触发setter方法,通知组件实例对应的watch方法实现视图的更新。
defineProperty也是有缺点的:
- 对于复杂对象需要深度监听、性能不好
- 对于对象的新增、删除属性无法监听这些操作。所以需要Vue.$set和Vue.$delete
- 需要重写数组的原生方法来实现数组监听
在vue3中使用proxy来替代defineProperty,它的优势:
- 可以直接监听整个对象、不需要遍历监听属性,性能有所提升。
- 可以直接监听数组的变化
- 拦截方法更多(13种)
Vue2和Vue3有哪些区别
Vue2使用的是选项式API ,Vue3使用组合式API,提高代码可维护性和复用性。Vue3使用Proxy代理替代Object.defineProperty实现了新的响应式系统,提高了性能。Vue3引入了Teleport组件,可以将DOM元素渲染到DOM树的其他位置,用于创建模态框、弹出框等。Vue3全局API名称发生了变化,同时新增了watchEffect、Hooks等功能Vue3对TypeScript的支持更加友好Vue3核心库的依赖更少,减少打包体积
reactive 对比 ref
- 都是用来生成响应式数据
- 不同点
- reactive不能处理简单类型的数据
- ref参数类型支持更好,但是必须通过.value做访问和修改
- ref函数内部的实现依赖于reactive函数
SPA和多页面有什么区别
区别
- 页面加载方式:在多页面应用中,每个页面都是独立的 HTML 文件,每次导航时需要重新加载整个页面。而在
SPA中,初始加载时只加载一个 HTML 页面,后续的导航通过JavaScript动态地更新页面内容,无需重新加载整个页面。 - 用户体验:
SPA提供了流畅、快速的用户体验,因为页面切换时无需等待整个页面的重新加载,只有需要的数据和资源会被加载,减少了页面刷新的延迟。多页面应用则可能会有页面刷新的延迟,给用户带来较长的等待时间。 - 代码复用:
SPA通常采用组件化开发的方式,可以在不同的页面中复用组件,提高代码的可维护性和可扩展性。多页面应用的每个页面都是独立的,组件复用的机会较少。 - 路由管理:在多页面应用中,页面之间的导航和路由由服务器处理,每个页面对应一个不同的
URL。而在SPA中,前端负责管理页面的导航和路由,通过前端路由库(如React Router或Vue Router)来管理不同路径对应的组件。 - SEO(搜索引擎优化):由于多页面应用的每个页面都是独立的 HTML 文件,搜索引擎可以直接索引和抓取每个页面的内容,有利于搜索引擎优化。相比之下,
SPA的内容是通过JavaScript动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容,需要采取额外的优化措施。 - 服务器负载:
SPA只需初始加载时获取HTML、CSS和JavaScript文件,后续的页面更新和数据获取通常通过 API 请求完成,减轻了服务器的负载。而多页面应用每次导航都需要从服务器获取整个页面的内容。
优点
- 可以实现实时更新和动态加载内容,使用户可以快速地与应用程序交互。
SPA通常采用组件化开发的方式,提高了代码的可维护性和可扩展性。- 只有初始页面加载时需要从服务器获取
HTML、CSS和JavaScript文件,减轻了服务器的负载。
缺点:
首次加载时需要下载较大的js文件,这可能导致初始加载时间较长。
由于
SPA的内容是通过js动态生成的,搜索引擎的爬虫可能难以优化。内存占用高。
Vue的性能优化有哪些
编码阶段
v-if和v-for不一起使用v-for保证key的唯一性- 使用
keep-alive缓存组件 v-if和v-show酌情使用- 路由懒加载、异步组件
- 图片懒加载
- 节流防抖
- 第三方模块按需引入
打包优化
- 压缩代码
- 使用CDN加载第三方模块
- 抽离公共文件
用户体验
- 骨架屏
- 客户端缓存
SEO优化
- 预渲染
- 服务端渲染
- 合理使用
meta标签
Computed 和 Watch 的区别
computed计算属性,通过对已有的属性值进行计算得到一个新值。它需要依赖于其他的数据,当数据发生变化时,computed会自动计算更新。computed属性值会被缓存,只有当依赖数据发生变化时才会重新计算,这样可以避免重复计算提高性能。
watch用于监听数据的变化,并在变化时执行一些操作。它可以监听单个数据或者数组,当数据发生变化时会执行对应的回调函数,和computed不同的是watch不会有缓存。
常见的事件修饰符及其作用
.stop阻止冒泡.prevent阻止默认事件.capture:与事件冒泡的方向相反,事件捕获由外到内;- .
self:只会触发自己范围内的事件,不包含子元素; .once:只会触发一次。
v-if和v-show的区别
v-if条件渲染,v-show条件显示。v-if切换条件时会通过响应式系统销毁或渲染整个条件块,有更高的切换消耗,适用于初始不存在或不频繁切换可见性的组件,比如需要一定权限才显示的模块; v-show通过设置元素display属性控制显示隐藏,更高的初始渲染消耗,适用于频繁切换可见性的组件,比如弹窗。
如何设置动态路由
- params传参
- 路由配置:
/index/:id - 路由跳转:
this.$router.push({name: 'index', params: {id: "zs"}}); - 路由参数获取:
$route.params.id - 最后形成的路由:
/index/zs
- 路由配置:
- query传参
- 路由配置:
/index正常的路由配置 - 路由跳转:
this.$rouetr.push({path: 'index', query:{id: "zs"}}); - 路由参数获取:
$route.query.id - 最后形成的路由:
/index?id=zs
- 路由配置:
区别
获取参数方式不一样,一个通过
$route.params,一个通过$route.query参数的生命周期不一样,
query参数在URL地址栏中显示不容易丢失,params参数不会在地址栏显示,刷新后会消失
路由守卫
- 全局前置钩子:
beforeEach、beforeResolve、afterEach - 路由独享守卫:
beforeEnter - 组件内钩子:
beforeRouterEnter、beforeRouterUpdate、beforeRouterLeave
slot是什么?有什么作用?
slot插槽,一般在封装组件的时候使用,在组件内不知道以那种形式来展示内容时,可以用slot来占据位置,最终展示形式由父组件以内容形式传递过来,主要分为三种:
- 默认插槽:又名匿名插槽,当
slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。 - 具名插槽:带有具体名字的插槽,也就是带有
name属性的slot,一个组件可以出现多个具名插槽。 - 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
$nextTick 原理及作用
Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。 nextTick是将回调函数放到一个异步队列中,保证在异步更新DOM的watcher后面,从而获取到更新后的DOM。
因为在created()钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在nextTick()的回调函数中。
虚拟DOM就一定比真实DOM更快吗
虚拟DOM不一定比真实DOM更快,而是在特定情况下可以提供更好的性能。
在复杂情况下,虚拟DOM可以比真实DOM操作更快,因为它是在内存中维护一个虚拟的DOM树,将真实DOM操作转换为对虚拟DOM的操作,然后通过diff算法找出需要更新的部分,最后只变更这部分到真实DOM就可以。在频繁变更下,它可以批量处理这些变化从而减少对真实DOM的访问和操作,减少浏览器的回流重绘,提高页面渲染性能。
而在一下简单场景下,直接操作真实DOM可能会更快,当更新操作很少或者只是局部改变时,直接操作真实DOM比操作虚拟DOM更高效,省去了虚拟DOM的计算、对比开销。
Diff 算法
Diff 算法(差异算法)是前端框架(如 React、Vue)用来 高效更新 DOM 的核心机制。它的核心思想是:对比新旧虚拟 DOM(或 VNode)的差异,只更新真正变化的部分,而不是全部重新渲染,从而提升性能。
为什么需要 ?
- 直接操作 DOM 很慢:浏览器渲染 DOM 的开销很大,频繁更新会导致页面卡顿。
- 全量更新浪费资源:如果数据变化时直接重新渲染整个页面,性能会很差。
基本流程
(1)虚拟 DOM(Virtual DOM)
- 框架(如 React/Vue)会先用 JavaScript 对象(虚拟 DOM)描述页面结构。
- 数据变化时,生成新的虚拟 DOM,然后和旧的虚拟 DOM 进行 对比(Diff)。
(2)Diff 对比规则
① 同级比较(Tree Diff)
- 只比较同一层级的节点,不会跨层级对比(减少计算量)。
- 如果节点类型不同(如
div→span),直接销毁重建。
② 列表对比(List Diff / Key 优化)
- **给列表元素加
key**(如key={item.id}),帮助框架识别哪些元素是新增、移动或删除的。 - 如果没有
key,框架会按顺序对比,可能导致不必要的 DOM 操作。
③ 组件对比(Component Diff)
JavaScript 的 Event Loop(事件循环)
JavaScript 是单线程的,意味着它一次只能执行一个任务。
Event Loop(事件循环) 就是 JS 处理异步任务的机制,它的核心逻辑是JS 按顺序执行同步代码(函数调用等)。异步任务完成后,它们的回调函数会被放入队列。队列又分成宏任务队列和微任务队列。宏任务队列包括 setTimeout、setInterval、IO、ui渲染事件的回调函数。微任务队列包括Promise.then、MutationObserver 等回调,优先级更高。
- 当调用栈为空时,先检查微任务队列,依次执行所有微任务。
- 然后从宏任务队列取一个任务(如
setTimeout回调)执行。 - 重复这个过程。
垃圾回收机制
标记清除算法 JavaScript 中最常用的垃圾收集方式
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
该算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始(在JS中就是全局对象)扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
此算法可以分为两个阶段,一个是标记阶段(mark),一个是清除阶段(sweep)。
- 标记阶段,垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。
- 清除阶段,垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作。
当内存耗尽时,程序将会被挂起,垃圾回收开始执行。
标记清除算法缺陷
- 那些无法从根对象查询到的对象都将被清除
- 垃圾收集后有可能会造成大量的内存碎片
引用计数算法
这是最初级的垃圾收集算法.现在已经没有浏览器会用这种算法.
此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。
引用计数缺陷
该算法有个限制:无法处理循环引用。如果两个对象被创建,并互相引用,形成了一个循环。它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。