1、你们打包js的过程是怎么样的? 怎么注入到你们框架里的?
打包流程:
- 选择打包工具(如 Webpack、Vite)。
- 配置入口文件、输出目录、加载器和插件。
- 运行打包命令生成打包文件。
注入框架:
- 在 HTML 文件中直接引入打包后的脚本。
- 在框架入口文件中加载打包后的代码。
- 使用插件(如
html-webpack-plugin)自动注入脚本。
优化: 代码压缩、Tree Shaking、缓存等。
2、组件库、脚手架、造轮子相关事情做过哪些?
用Element封装过一些组件,如表单、列表、详情、弹窗和抽屉等等;脚手架和造轮子暂无。
3、不同屏幕的大小适配是怎么做的?
- 媒体查询;
- 使用 rem 单位;
- flexible 适配方案(rem模拟vw 和 viewport的scale和width);
- viewport 适配方案(设置meta标签 和 使用 postcss-px-to-viewport px转vw)。
4、要让子元素的透明度为 1,即使父元素的透明度小于 1,可以使用以下方法:
- 使用
background-color的 RGBA 值,仅让背景不透明。 - 将子元素移出父元素的 DOM 结构,或通过伪元素覆盖父元素的透明度。
- 使用
filter或backdrop-filter替代父元素的opacity。
5、用 const 定义了一个对象后,能用另一个对象替换吗?
- 使用
const定义的对象不能被重新赋值为另一个对象,否则会抛出错误。 - 但可以自由地修改对象的属性(包括添加、删除或修改)。
- 如果需要“替换”对象,可以通过清空对象并重新赋值,或者使用
Proxy来实现更灵活的控制。
6、JS内存泄漏有哪些情况?
- 全局变量的无限制增长(避免使用或限制大小;尽量使用局部变量或模块作用域来封装数据);
- 未清理的事件监听器(使用removeEventListener清理;使用once选项为事件监听器设一次性执行);
- 闭包中的不当引用(确保只捕获必要的变量;不需要后显式设变量为 null);
- 定时器和未清理的引用(使用 clearInterval 和 clearTimeout);
- 被移除的 Dom 元素未清理(移除后,确保清除所有对其引用);
- 循环引用(显式打破,如设其中一个为 null)。
7、一个大数据的循环,可能需要10秒执行完,但是我又要每5秒去执行一下这个程序?
- 使用
setInterval和标志位控制: 通过setInterval定时执行程序,并使用一个标志位(如isRunning)确保上一次执行完成后再开始下一次执行。 - 使用递归
setTimeout:通过递归调用setTimeout,确保每次执行完成后才启动下一次执行。 - 使用多线程(Worker):将大数据循环放到 Web Worker 中执行,避免阻塞主线程,同时通过
setInterval定时触发 Worker。 - 任务队列:将大数据循环分解为多个小任务,放入队列中依次执行,同时通过
setInterval定时触发任务。 - 使用异步编程(
async/await):将大数据循环封装为异步函数,通过setInterval定时触发,并确保每次执行完成后再开始下一次执行。
8、前端加密手段和对应使用场景有哪些?
- 对称加密:使用相同的密钥进行加密和解密,适合大量数据的加密。常见的对称加密算法是 AES(高级加密标准),可使用 CryptoJS。适用于 表单数据加密 和 文件传输加密。
- 非对称加密:非对称加密使用一对密钥(公钥和私钥),公钥用于加密,私钥用于解密。常见的非对称加密算法是 RSA,可使用 JSEncrypt。适用于 适合加密少量数据如密钥交换 和 验证数据来源的真实性。
- 哈希算法:是一种不可逆的加密算法,常用于数据完整性校验。常见的哈希算法包括 MD5 和 SHA-256,可使用 CryptoJS。适用于 验证数据是否被篡改 和 存储密码的哈希值。
- Base64 编码:一种编码方式,而非加密算法,主要用于在传输过程中将二进制数据转换为可打印字符,可使用 btoa 和 atob。适用于 在 URL、Cookie 或网页中传输少量二进制数据。
- Web Crypto API:是浏览器原生支持的加密 API,安全性高,性能优于 JavaScript 加密库。适用于 需要高安全性和性能的场景。
- HTTPS 协议:它通过 SSL/TLS 协议对数据传输进行加密,确保数据在传输过程中不被窃取或篡改。适用于 所有需要安全传输数据的场景。
9、vue中,created 和 watch 谁先执行?
created 生命周期钩子
created 是 Vue 实例创建完成后立即执行的生命周期钩子。此时,Vue 实例已经完成数据初始化,但 DOM 还未挂载。created 通常用于初始化数据、设置默认值或进行一些非 DOM 相关的逻辑处理。
watch
watch 是 Vue 的一个响应式系统功能,用于监听数据的变化,并在数据发生变化时执行指定的回调函数。watch 的回调函数只有在被监听的数据发生变化时才会触发。
在 Vue 的初始化过程中,created 生命周期钩子会在实例创建完成后立即执行,而 watch 的回调函数则需要等到被监听的数据发生变化时才会触发。
因此,created 生命周期钩子总是先于 watch 的回调函数执行。
10、在 Vue 里面挂载全局方法?
- Vue.prototype;
- 插件形式;
- Mixin全局混入;
- Vue3 中的 app.config.globalProperties。
11、解释一下 React 虚拟DOM的工作原理?
虚拟 DOM 是真实 DOM 的 JavaScript 对象表示,它是对浏览器 DOM 的抽象。虚拟 DOM 的结构和真实 DOM 类似,但它是轻量级的,并且操作虚拟 DOM 比操作真实 DOM 更高效。工作流程如下:
- 构建虚拟 DOM(Mounting):当 React 组件首次渲染时,React 会根据组件的 JSX 或
render()方法构建一个虚拟 DOM 树。这个过程称为“挂载”(Mounting)。 - 将虚拟 DOM 转换为真实 DOM:构建完成后,React 会将虚拟 DOM 转换为真实 DOM,并将其插入到浏览器的 DOM 树中。这个过程是通过 React 的
ReactDOM.render()方法完成的。 - 更新虚拟 DOM(Re-rendering):当组件的
props或state发生变化时,React 会重新执行组件的render()方法或 JSX 表达式,生成一个新的虚拟 DOM 树。这个过程称为“更新”(Re-rendering)。 - 比较虚拟 DOM(Diffing):React 会将新生成的虚拟 DOM 树与之前的虚拟 DOM 树进行比较,找出它们之间的差异。这个过程称为“差异计算”(Diffing)。
- 更新真实 DOM(Patching):React 根据 Diffing 的结果,将差异应用到真实 DOM 上。这个过程称为“打补丁”(Patching)。React 只会更新发生变化的部分,而不是重新渲染整个 DOM,从而提高性能。
12、React 虚拟DOM 对性能方面有什么帮助?
- 减少直接操作真实 DOM:通过在内存中操作虚拟 DOM,避免了频繁的重绘和重排。
- 批量更新:将多个状态更新合并为一次,减少了触发重绘和重排的次数。
- 高效的差异计算:通过 Diffing 算法,只更新发生变化的部分,减少了不必要的 DOM 操作。
- 减少重绘和重排:通过最小化 DOM 操作,减少了对页面布局的影响。
- 跨平台复用:虚拟 DOM 的设计使得 React 的核心逻辑可以复用于不同平台。
13、了解 React 的错误边界吗?
React 的错误边界(Error Boundaries)是一种特殊的组件,用于捕获其子组件树中的 JavaScript 错误,防止这些错误导致整个应用崩溃。错误边界可以在渲染期间、生命周期方法以及子组件的构造函数中捕获错误,并展示备用的用户界面(UI),而不是显示错误堆栈或导致应用崩溃。
错误边界的限制
- 只能捕获后代组件的错误:错误边界无法捕获自身或兄弟组件中的错误。
- 无法捕获异步错误:例如在
setTimeout或Promise中抛出的错误。 - 无法捕获事件处理器中的错误:需要在事件处理器中手动捕获错误。
- 无法捕获服务端渲染期间的错误。
最佳实践
- 合理组织组件结构:避免过度使用错误边界,只在关键路径上使用。
- 记录错误日志:在
componentDidCatch中将错误信息上报到服务器。 - 显示友好的错误信息:提升用户体验。
- 恢复机制:可以通过状态控制提供重试按钮,恢复正常的 UI。
错误边界是 React 提供的一种强大的错误处理机制,能够有效提升应用的稳定性和用户体验
14、项目里如何使用 TS?
主要用到 Interface、Type 和 Enum 等属性。
15、介绍一下路由模式?
-
哈希模式: 哈希模式下,URL 中 # 后面的部分不会被发送到服务器,由前端代码解析和处理。当哈希值发生变化时,浏览器不会重新加载页面,而是触发 hashChange 事件,前端路由库可以监听该变化来切换视图。
-
历史模式: 该模式时通过 H5 的 History API 实现的,主要使用 pushState 和 replaceState 方法来修改浏览器的历史记录,而不会重新加载页面。前端路由库会监听浏览器的 popState 事件(如用户点击浏览器的后退按钮),并根据当前的 URL 状态切换视图。
总结
- 哈希模式:简单易用,兼容性好,但 URL 不美观,不利于 SEO。
- 历史模式:URL 美观,有利于 SEO,但需要服务器支持,实现相对复杂且兼容性差。
16、封装过哪些 hooks?
17、阐述一下 useMemo 和 useCallback 的原理以及二者区别?
- useMemo: 用于缓存一个值或计算结果,避免在组件重新渲染时,重复执行相同的计算。接收两个参数:一、函数,用于计算需要缓存的值;二、依赖数组,用于指定哪些变量会影响缓存值的重新计算。
- 当组件首次渲染时,
useMemo会调用传入的函数,并缓存其返回值。 - 在后续的渲染中,只有当依赖数组中的值发生变化时,
useMemo才会重新调用函数并更新缓存值。否则,它会直接返回上一次缓存的值。
- useCallback: 用于缓存一个函数,避免在组件重新渲染时重复创建函数。接收两个参数:一、函数,需要缓存的函数;二、依赖数组,用于指定哪些变量会影响函数的重新创建。
- 在组件首次渲染时,
useCallback会返回传入的函数。 - 在后续的渲染中,只有当依赖数组中的值发生变化时,
useCallback才会重新创建并返回一个新的函数。否则,它会返回上一次缓存的函数。
区别:useMemo优化计算逻辑,useCallback优化函数引用。
18、阐述一下浏览器的缓存机制?
工作原理:
- 请求资源:用户输入网址或点击链接,浏览器发起资源请求;浏览器首先检查本地缓存中是否存在该资源。
- 检查缓存策略:如果资源在缓存中,浏览器会根据缓存策略判断资源是否有效;若有效(强缓存命中),浏览器直接使用本地缓存,不向服务器发送请求;若无效,浏览器会向服务器发送请求,通过协商缓存验证资源是否更新。
- 服务器响应:若资源未更新,服务器返回 304 状态码即未修改,浏览器继续使用本地缓存;若资源已更新,服务器返回 200 状态码,并提供新的资源内容,浏览器更新缓存并加载新的资源。
- 存储资源:浏览器将新获取的资源存储在本地缓存中,以便后续继续访问。
缓存类型
一. 强缓存:指浏览器直接从本地缓存中读取资源,而不会向服务器发送请求。基于以下两个 HTTP 响应头:
Expires:指定资源的过期时间,格式为GMT时间戳。Cache-Control:更灵活的缓存控制策略,优先级高于Expires。
工作流程:
- 浏览器检查本地缓存中是否存在资源。
- 如果存在且未过期(根据
Cache-Control或Expires判断),直接使用本地缓存。 - 如果资源过期或不存在,进入下一步(协商缓存)。
二. 协商缓存:当强缓存失效后,浏览器会向服务器发送请求,通过协商缓存来判断资源是否需要重新下载。协商缓存基于以下两个HTTP头:
-
Last-Modified和If-Modified-Since:- 服务器返回资源的最后修改时间(
Last-Modified)。 - 客户端在请求时带上
If-Modified-Since,表示上次获取资源的时间。 - 如果资源没有被修改,服务器返回
304 Not Modified,客户端使用本地缓存。
- 服务器返回资源的最后修改时间(
-
ETag和If-None-Match:- 服务器返回资源的唯一标识(
ETag),通常是一个哈希值。 - 客户端在请求时带上
If-None-Match。 - 如果资源没有变化,服务器返回
304 Not Modified。
- 服务器返回资源的唯一标识(
19、https 是如何工作的?
1. 数据加密
HTTPS使用SSL/TLS协议对数据进行加密,确保数据在传输过程中的机密性。具体来说:
- 对称加密:用于实际数据的加密和解密,效率高,适合大量数据传输。
- 非对称加密:用于密钥交换过程,确保对称加密的密钥在传输过程中不被窃取。
这种混合加密机制既保证了数据的安全性,又提高了加密效率。
2. 身份验证
HTTPS通过数字证书验证服务器的身份,确保用户连接到的是合法的服务器,而不是被中间人攻击的伪造服务器。证书由受信任的证书颁发机构(CA)签发,包含服务器的公钥、域名、有效期等信息。
客户端在建立连接时会验证服务器证书的有效性,包括证书的颁发机构是否可信、证书是否在有效期内等。
3. 数据完整性
HTTPS使用哈希算法和消息认证码(MAC)来确保数据在传输过程中未被篡改。服务器在发送数据前会生成数据的摘要,并对摘要进行签名。客户端收到数据后会重新计算摘要并验证签名,从而确保数据的完整性和来源。
4. 防止中间人攻击
通过加密通信和身份验证机制,HTTPS可以有效防止中间人攻击。即使攻击者能够拦截数据,也无法解密或篡改数据,也无法伪造有效的数字证书。
5. 应用场景
HTTPS广泛应用于需要保护用户敏感信息的场景,例如电子商务、网上银行、在线支付等。此外,搜索引擎对HTTPS网站的青睐也促使更多网站采用HTTPS,以提高网站的安全性和可见度。
总结
HTTPS通过加密传输、身份验证、数据完整性保护等机制,提供了比HTTP更高的安全性和隐私保护。它有效防止了数据被窃取、篡改和中间人攻击,是现代互联网中保障数据安全的重要手段。
20、cdn 是如何工作的?
- 用户请求:用户通过浏览器访问网站的某个资源(如图片)。
- DNS解析:用户的请求首先到达CDN的DNS服务器,CDN根据用户的地理位置将请求路由到最近的边缘服务器。
- 内容检索:
- 如果边缘服务器上有缓存的内容,直接返回给用户。
- 如果没有缓存,边缘服务器从源服务器获取内容,缓存后返回给用户。
- 缓存更新:当缓存的内容过期或被手动清除时,CDN会重新从源服务器获取最新的内容。
21、阐述一下浏览器的事件循环机制?
-
基本概念: 事件循环是 JS 运行时环境的一部分,它不断检查任务队列,并将队列中任务推入调用栈执行。事件循环的主要目的是确保调用栈在合适的时间处理任务队列中的任务。
-
组成部分:
- 调用栈(Call Stack):后进先出的数据结构,用于存储当前正在执行的函数;每次调用一个函数时,调用栈会添加一个栈帧,执行完毕后被弹出;若为空,表示当前没有正在执行的函数。
- 任务队列(Task Queue):先进先出的数据结构,用于存储等待执行的任务;这些任务通常由异步操作(如
setTimeout、Promise等)触发的回调函数;事件循环会不断检查任务队列,当调用栈为空时,将队列中的任务推入调用栈中执行。 - 事件循环(Event Loop):事件循环是一个无限循环,它不断检查调用栈是否为空,如果为空,则从任务队列中取出任务并推入调用栈执行;事件循环确保了异步操作能够在合适的时间被处理,而不会阻塞主线程。
- 工作流程:
- 执行同步代码:浏览器首先执行调用栈中的同步代码;同步代码执行完毕后,调用栈变为空。
- 检查任务队列:当调用栈为空时,事件循环检查任务队列;若有任务,将其取出放入调用栈执行。
- 重复上述过程:事件循环会不断重复上述过程,确保调用栈和任务队列中的任务能够被正确处理。
22、阐述一下 JS 的原型和原型链?
原型:在 JS 中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。
原型链:当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的属性,于是就这样一直找下去,也就是原型链的概念。
23、详细解释 Vue3 相对 Vue2 的改动。
- API:Options Api 和 Composition Api
Option Api:包含一个描述组件(data、methods、props等)的对象 options;Api 开发复杂组件,同一个功能逻辑代码被拆分到不同选项;使用 mixin 重用公用代码,易造成命名冲突,且数据来源不清晰。 Composition Api:Vue3 新增的一组 api,是基于函数的 api,可以更灵活的组织组件的逻辑。解决 options api 在大型项目中不好拆分和重用的问题。
- 响应式:Proxy 和 Object.defineProperty
Object.defineProperty:vue2 初始化的时候,对 data 中的每个属性使用 defineProperty 调用 getter 和 setter 使之变为响应式对象。
Proxy:vue3 使用 Proxy 重写响应式。Proxy 的性能强于 defineProperty,Proxy 可以拦截属性的访问、赋值、删除等操作,不需要初始化的时候遍历所有属性,另外有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性。
优势:可以监听动态新增的属性;可以监听删除的属性;可以监听数组的索引和 length 属性。
- 编译优化:优化编译和重写虚拟 dom,让首次渲染和更新 dom 性能有更大的提升。vue2 通过标记静态根节点优化 diff,vue3 标记和提升所有静态根节点,diff 的时候只需要对比动态节点内容。
Fragments:模板里面不用创建唯一根节点,可以直接放同级标签和文本内容;
Patch Flag:跳过静态节点,直接对比动态节点,缓存事件处理函数。
- 源码体积优化:移除了一些不常用的 api 如:inline-template、filter 等,使用 tree-shaking。
24、Cache-Control 的 max-age 和 Expires 的区别?
Expires:适用于简单的缓存策略,尤其是当资源的过期时间是固定的;在较旧的 HTTP/1.0 环境中,Expires 是唯一可用的缓存头。
Cache-Control: max-age:更现代,适用于需要更灵活的缓存策略。可以与其他 Cache-Control 指令结合,实现复杂的缓存行为(如私有缓存、强制验证等)。
25、Webpack 的构建流程
- 初始化参数:解析 webpack 配置参数,合并 shell 传入和 webpack.config.js 文件配置的参数,形成最后的配置结果。
- 开始编译:上一步得到的参数初始化 compiler 对象,注册所有配置的插件,插件监听 webpack 构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译。
- 确定入口:从配置的 entry 入口,开始解析文件构建 AST 语法树,找出依赖,递归下去。
- 编译模块:递归中根据文件类型和 loader 配置,调用所有配置的 loader 对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
- 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
26、nextTick 有什么作用?它的实现原理是什么?
nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout 的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。
nextTick 方法用于在下次 DOM 更新循环结束之后执行延迟的回调函数。它可以用来确保在更新 DOM 后执行某些操作,如操作更新后的 DOM 元素或获取更新后的计算属性的值。通常在需要等待 DOM 更新完成后进行操作的情况下使用 nextTick。
nextTick 的实现原理是基于 JavaScript 的事件循环机制,利用了微任务和宏任务的特性。它利用了浏览器的异步队列机制,通过在当前事件循环结束后,将回调函数放入微任务队列,以确保在 DOM 更新完成后再执行这些回调。具体而言,Vue 会根据环境优先选择微任务(如 Promise.then)来实现异步更新,以确保高优先级的任务能够及时执行。
执行流程:
- 数据变化:当数据发生变化时,Vue 不会立即更新 DOM,而是将这些更新放入一个队列中,等待所有同步任务完成后再统一执行。
- 调用 nextTick:当调用 nextTick 时,Vue 会将传入的回调函数添加到一个回调队列中。
- 异步执行回调:在当前事件循环结束后,Vue 会根据环境选择合适的异步方法(如 Promise.then)来执行回调队列中的所有函数。
27、介绍一下 ESM 和 CJS 的区别
CJS
- 同步加载:CJS 是同步加载模块的,模块在代码执行时立即加载。
- 文件系统依赖:CJS 依赖于文件系统路径,模块路径必须是相对路径或绝对路径。
- 运行时解析:模块路径在运行时解析,动态加载模块。
ESM:
- 异步加载:ESM 是异步加载模块的,模块在解析时不会立即加载,而是等到实际需要时才加载。
- 静态解析:ESM 的导入路径必须是静态字符串,不能使用动态路径。
- 支持动态导入:ESM 支持动态导入(
import()),返回一个 Promise,允许按需加载模块。
28、unplugin-auto-import 按需加载的原理是什么?
核心功能:通过静态代码分析,自动识别代码中使用的 API,并在构建时注入相应的 import 语句。
初始化预设:插件的核心逻辑之一是初始化预设。预设的作用是告诉插件应该自动导入哪些内容。预设可以是字符串(如 'vue'),也可以是对象(用于自定义导入)。
扫描文件并注入 import 语句:
- 过滤文件:使用正则表达式过滤出需要处理的文件,如
.js、.ts、.vue等。 - 扫描代码:读取文件中的代码,提取出未定义但被使用的标识符。
- 匹配预设:将提取的标识符与预设的
importMap对比,确定需要注入的模块。 - 注入
import:在文件顶部注入对应的import语句。
生成 TypeScript 类型声明:如果项目使用 TypeScript,插件会生成一个全局类型声明文件(如 auto-imports.d.ts),确保自动导入的变量在 TypeScript 编译时不会报错。
ESLint 配置:为了防止 ESLint 报错(如变量未定义),插件会生成一个 ESLint 配置文件(如 .eslintrc-auto-import.json),将自动导入的变量声明为全局变量。
工作流程:
- 构建开始时:初始化预设并生成类型声明文件。
- 文件转换时:扫描文件,注入
import语句。 - 构建结束时:完成所有文件的转换。
29、React Fiber 遍历 Dom 结构的顺序?
React Fiber:是 React 的核心调度和渲染引擎,它通过将渲染过程分解为可中断的单元(Fiber 节点),实现了高效的增量渲染和任务调度。
遍历顺序:
- 协调阶段(Reconciliation) :
- 使用 深度优先遍历(DFS) 。
- 自顶向下,先子节点后兄弟节点。
- 回溯机制确保逐层处理更新逻辑。
- 提交阶段(Commit Phase) :
- 使用 深度优先遍历(DFS) 。
- 先子节点后父节点。
- 先处理插入和更新操作,再处理删除操作。
原因:
- 高效性:通过深度优先遍历,React 能够逐层处理组件的更新逻辑,避免重复计算。
- 一致性:先处理子节点后父节点的顺序,确保 DOM 的更新不会影响父节点的布局和样式计算。
- 可中断性:Fiber 架构允许在任务之间中断和恢复,使得 React 能够在浏览器的空闲时间执行更新操作,避免阻塞主线程。
30、watchEffect 和 watch 有什么区别?什么时候使用哪个?
watchEffect 用于监听响应式数据的变化,并在回调函数中执行相应的操作。它会自动追踪依赖,并在依赖变化时重新运行回调函数。
watch 用于监听指定的响应式数据,并在其变化时执行相应的操作。它可以精确地指定要监听的数据,并提供更多的配置选项。
一般来说,如果只需要监听一个响应式数据的变化并执行相应操作,可以使用 watchEffect;如果需要更细粒度的控制,可以使用 watch。
31、React Hooks 的原理是什么?
- Hooks的核心原理:允许在函数组件中使用状态和 React 特性,并保持了函数组件的简洁性。其实现基于 JavaScript 的闭包和数组机制,使得函数组件能够拥有状态和生命周期能力。
- 闭包的作用:在 JS 中,闭包可以捕获函数外部的变量,并在函数内部使用。React Hooks 利用了闭包的特性,将状态和生命周期函数存储在组件的闭包中,从而实现跨渲染的状态保持。
- 内部实现:React Hooks 的内部实现依赖于一个内部的“Hooks 队列”。每次调用一个 Hook(如
useState或useEffect)时,React 会将相关的状态或副作用信息存储在一个数组中。在组件渲染时,React 会按照调用顺序依次处理这些 Hooks。
- useState:用于在函数组件中添加状态。
内部实现:
- 初始化状态:在组件的首次渲染时,会将传入的初始值存储在一个内部变量中;
- 更新状态:在后续的渲染中,它会从内部存储中获取状态值,而不是每次都使用初始值;
- 返回状态和更新函数:返回一个数组,包含当前状态值和一个用于更新状态的函数。
- useEffect:处理副作用,类似于类组件的生命周期方法。
内部实现:
- 注册副作用:在组件渲染时,将副作用函数存储在内部队列中;
- 执行副作用:在组件渲染完成后,React 会依次执行队列中的副作用函数;
- 清理副作用:如果副作用函数返回一个清理函数,React 会在组件卸载或下次副作用执行前调用清理函数。
内部机制:
- 首次渲染:调用
useEffect,将副作用函数存储在内部队列中;组件渲染完成后,执行副作用函数。 - 依赖变化:如果依赖数组中的值发生变化,React 会重新执行副作用函数;在重新执行前,React 会调用上一次副作用函数返回的清理函数(如果有)。
- 组件卸载:React 会调用所有副作用函数返回的清理函数。
- React Hooks 的限制:
- 调用顺序:Hooks 必须在组件顶层调用,不能在循环、条件语句或嵌套函数中调用;
- 仅限函数组件:Hooks 只能在函数组件中使用,不能在类组件中使用;
- 学习曲线:对于习惯了类组件的开发者...
- 内部实现细节
- Hooks 队列:React 使用一个数组来存储每个 Hook 的状态和副作用信息。每次调用一个 Hook 时,React 会将相关信息存储在队列中;
- 索引机制: React 使用一个索引来跟踪当前正在处理的 Hook,每次调用一个 Hook 时,React 会递增索引;
- 闭包:Hooks 的状态和副作用信息存储在组件的闭包中,从而实现跨渲染的状态保持。
32、setState 是同步还是异步?
- 通常情况下,
setState是异步的,React会将多个状态更新合并成一个批量更新操作,进行统一处理,以优化性能。 - 特殊情况下,
setState会表现出同步的行为,例如在useEffect的回调函数中或使用flushSync时,会立即更新状态,并触发组件的重新渲染。 - 如果需要在状态更新后立即使用新的状态值,可以使用
setState的回调函数或useEffect。
33、创建 k8s 任务时,为什么需要镜像?
因为 k8s 是一个容器编排平台,它通过调度和管理容器来运行应用程序。容器镜像是部署到了 k8s 上的应用程序的打包形式。
- 封装环境:容器镜像包含了应用程序及其所有依赖项、库文件、工具等,确保了应用可以在任何环境中以相同的方式运行,无论是在开发、测试还是生产环境中。
- 隔离性:每个容器都是相互隔离的,这意味着不同容器内的进程不会互相干扰。这提供了更好的安全性,并有助于避免因软件冲突或资源竞争引起的问题。
- 版本控制与分发:镜像可以被打上标签(tag),这样就可以方便地进行版本管理和分发。不同的团队成员或系统可以根据特定需要拉取特定版本的镜像,保证了应用的一致性和可追溯性。
- 简化部署流程:使用镜像意味着你不需要在每个节点上手动安装和配置应用程序。只需要将镜像推送到镜像仓库,然后在 k8s 的 Pod 或 Job 配置中引用该镜像即可。
- 可移植性:容器化应用可以在任何支持容器技术的操作系统上运行,使得跨云服务提供商或者本地数据中心迁移变得更加容易。
- k8s 工作原理:k8s 并不直接执行代码;相反,它管理的是由容器镜像定义的 Pod(即最小部署单元)。当你创建一个 Job 资源时,实际上是指示 k8s 取启动一个或多个包含指定镜像的 Pod 来完成任务。
- 弹性与可扩展性:由于容器化应用是自包含的,k8s 可以很容易地根据需求扩缩容,自动调整运行实例的数量以匹配当前负载。
综上所述,镜像是 k8s 应用程序的基本构建块,它为应用提供了一种可靠且一致的方式来部署和运行在任何支持容器的环境中。没有镜像,k8s 就无法知道要运行什么以及如何运行。因此,在创建 k8s 任务时,必须指定一个镜像作为任务执行的基础。
34、react 如何优化?
- 组件的拆分与懒加载:
- 组件拆分:将大型组件拆分成更小的、可复用的组件,减少每次渲染的 DOM 节点数量,提升性能;
- 懒加载:使用
React.lazy和Suspense实现组件的按需加载,减少应用的初始加载时间。
- 使用
React.memo和PureComponent:
React.memo:对于函数组件,使用React.memo进行性能优化,记忆组件的渲染结果,仅在组件的 props 发生变化时才重新渲染;PureComponent:对于类组件,继承自React.PureComponent,通过浅比较 props 和 state 来避免不必要的渲染。
- Hooks 方案:
- useState:合理使用即少 setState 并多合并,避免在 Redux 中存储过多局部状态;
- useEffect:利用依赖数组精确控制副作用的触发时机,避免不必要的渲染或计算;
- useMemo & useCallback:
useMemo用于缓存计算结果,useCallback用于缓存函数,减少不必要的渲染。
-
合理使用 key 属性:在列表渲染时,给每个元素指定一个唯一的
key属性,帮助 React 识别哪些元素发生了变化、被添加或被移除,从而优化 DOM 的更新过程。 -
服务端渲染(SSR):对于首屏加载时间要求极高的应用,可以考虑使用服务器端渲染(SSR)。SSR 可以在服务器端生成 HTML,然后将生成的 HTML 直接发送给客户端,减少客户端的等待时间。
-
压缩和缓存:
- 代码压缩:使用 Webpack 的 TerserPlugin 对 React 应用进行代码压缩;
- 静态资源缓存:利用 Service Workers 和 HTTP 缓存头部对静态资源进行缓存,减少重复加载。
-
代码分割:通过将代码拆分成多个小块,实现按需加载,从而提高应用的性能。
-
Tree Shaking:确保项目配置了正确的
mode和package.json文件中的sideEffects属性,以移除未使用的代码。
35、react hooks 的闭包存在的问题是什么?
主要在于两种情况:useState 和 useEffect。
useState:主要由于其参数只在组件挂载时执行一次。如果在其中使用闭包,闭包中的变量值就会被缓存,意味着当在组件中更新状态时,闭包中的变量值不会随之更新。
useEffect:主要由于其中的函数是在每次组件更新时都会执行一次。若在其中使用闭包,则该闭包中的变量值也会被缓存。
从源码看:主要在于 hooks 的实现方式。在 React 内部,每个组件都有一个对应的 Fiber 对象,表示组件的渲染状态。这些 hooks 的实现都是基于这个 Fiber 对象的,并且会在 Fiber 对象中存储当前的状态值和更新状态的函数。
36、react hooks 的闭包为什么拿不到最新的值?
渲染机制:每次组件渲染时,都会基于当前的状态 state 和属性 props 生成一个新的组件实例。这个实例包含了当前渲染时的所有状态和属性的值。
拿不到的原因:当组件重新渲染时,新的状态和属性会被用于生成新的组件实例。然而,已经创建的闭包(如 useEffect 的回调函数或定时器回调)仍然引用着旧的组件实例中的状态和属性值。因此,这些闭包中的变量值不会自动更新为最新的值。
解决方案:
- 使用函数式更新确保状态的更新是基于最新的值。
- 确保
useEffect的依赖项完整。 - 使用
useRef保存最新的状态值。 - 使用
useCallback优化性能。
37、说一下 HTTP 和 HTTPS 的区别
- 端口分别为 80 和 443。
- HTTP 是超文本传输协议,信息是明文传输;HTTPS 是具有安全性的 SSL 加密传输协议。
- http 的连接简单且无状态;https 协议是由 ssl+http 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。
- https 的缓存不如 http 高效,会额外增加数据开销。
38、说一下 HTTP 1.0 / 1.1 / 2.0 / 3.0 的区别
HTTP 1.0
- 无状态,无连接;
- 短连接:每次发送请求都要重新建立tcp请求,即三次握手,非常浪费性能;
- 无host头域,也就是http请求头里的host;
- 不允许断点续传,而且不能只传输对象的一部分,要求传输整个对象。
HTTP 1.1
- 长连接,流水线,使用connection:keep-alive使用长连接;
- 请求管道化;
- 增加缓存处理(新的字段如cache-control);
- 增加Host字段,支持断点传输等;
- 由于长连接会给服务器造成压力。
HTTP 2.0
- 多路复用,无需多个TCP连接,因为其允许在单一的HTTP2连接上发起多重请求,因此可以不用依赖建立多个TCP连接。
- 二进制分帧,将所有要传输的消息采用二进制编码,并且会将信息分割为更小的消息块。
- 头部压缩,用HPACK技术压缩头部,减小报文大小
- 服务端推送,服务端可以在客户端发起请求前发送数据,换句话说,服务端可以对客户端的一个请求发送多个相应,并且资源可以正常缓存
HTTP 3.0
- 基于google的QUIC协议,而quic协议是使用udp实现的;
- 减少了tcp三次握手时间,以及tls握手时间;
- 解决了http 2.0中前一个stream丢包导致后一个stream被阻塞的问题;
- 优化了重传策略,重传包和原包的编号不同,降低后续重传计算的消耗;
- 连接迁移,不再用tcp四元组确定一个连接,而是用一个64位随机数来确定这个连接;
- 更合适的流量控制
39、如何防止 csrf 和 xss 发生?
跨站脚本攻击(XSS):
- 原理:攻击者通过在网页中插入恶意脚本,当其他用户浏览该网页时,这些恶意脚本会在用户的浏览器中执行,从而盗取用户信息、篡改网页内容或其他恶意操作。
- 类型:
- 反射型:恶意脚本通过 url 参数或其他输入点传递到服务器,然后直接返回给用户。常发生于搜索结果页、错误消息页等动态生成的页面上。
- 存储型:恶意脚本被存储在服务器上,当其他用户访问相关页面时,脚本会被执行。常见于评论系统、论坛、博客等允许用户提交的内容的场景。
- DOM 型:恶意脚本通过修改页面的 DOM 结构来执行,通常发生在客户端 JS 的代码中。该类型攻击不涉及服务端,完全在客户端。
- 措施:
- 输入验证和过滤:输入验证和过滤是防止 XSS 攻击的第一道防线。开发者需要对用户提交的所有数据进行严格的验证和过滤,确保数据中不包含恶意脚本。常用的验证方法包括正则表达式匹配、黑名单过滤等。
- 输出编码:在将数据输出到 HTML 页面时,对数据进行适当的编码,可以有效防止恶意脚本的执行。常见的编码方法包括 HTML 实体编码、JavaScript 编码等。
- Content Security Policy (CSP):一种安全机制,用于限制网页可以加载的资源。通过设置 CSP 头,可以有效地防止恶意脚本的执行。CSP 头可以通过 HTTP 响应头或
<meta>标签设置。 - HTTPOnly Cookie:设置 Cookie 的
HttpOnly标志,可以防止 JavaScript 访问 Cookie,从而减少 XSS 攻击的风险。HttpOnly标志可以在设置 Cookie 时添加。 - X-XSS-Protection:一种浏览器内置的防护机制,可以自动检测并阻止某些类型的 XSS 攻击。虽然现代浏览器已经逐步淘汰了这个头,但在一些旧版浏览器中仍然有效。
- X-Content-Type-Options:设置
X-Content-Type-Options头为nosniff,可以防止浏览器猜测 MIME 类型,从而减少因 MIME 类型误判导致的 XSS 攻击。
跨站请求伪造(CSRF):
- 原理:是一种攻击手段,攻击者诱导用户在已登陆的网站上执行非预期的操作。攻击者通过构造一个恶意请求,利用用户已有的认证信息(如 Cookie),在用户不知情的情况下提交表单或执行其他操作。其攻击关键在于攻击者能够预测用户的认证信息,并构造出有效的请求。
- 措施:
- CSRF Token:在表单中添加一个随机生成的 CSRF Token,并在服务器端进行验证。每次请求时,服务器会检查其是否有效,从而防止非法请求。CSRF Token 可以通过隐藏字段、HTTP 头等方式传递。
- SameSite Cookie:设置 Cookie 的 SameSite 属性为 Lax 或 Strict,可以限制 Cookie 只能在同站请求中发送 Cookie,而 Strict 模式则完全禁止跨站请求中的 Cookie 发送。
- Referer Check:检查请求的 Referer 头,确保请求来自合法的来源。若为空或来自未知来源,可以拒绝请求。
- 双重提交 Cookie:在请求中包含一个与 Cookie 一致的 Token,服务器端验证两个 Token 是否匹配,以防止攻击者在跨站请求中伪造 Cookie。
40、简述一下 Redux 的设计方式
Redux 的核心设计理念是将应用的状态存储在一个集中化的存储(store)中,并通过一系列的规则来管理状态的更新。
- Redux 的三大原则
- 单一数据源:整个应用的状态存储在一个对象树(state tree)中,这个对象树通常被称为“store”。所有的状态都集中管理,使得状态的调试和跟踪更加容易。
- 状态是只读的:状态不能直接修改,而是通过触发“动作”(actions)来描述发生了什么。每个动作都是一个 JavaScript 对象,通常包含一个 type 属性,用于描述动作的类型。
- 使用纯函数来执行状态更新:状态的更新由“reducer”函数负责。Reducer 是一个纯函数,它接收当前的状态和一个动作,返回新的状态。Reducer 必须是纯函数,即相同的输入总是产生相同的输出,且没有副作用。
- 工作流程
- 创建 Store:使用
createStore函数创建一个 Redux store,传入一个 reducer 函数。 - 派发 Actions:通过调用
store.dispatch()方法来派发一个动作,通知 Redux 更新状态。 - 订阅 Store 的变化:可以订阅 Redux store 的变化,当状态更新时,执行相应的回调函数。
- 获取当前的状态:通过
store.getState()方法获取当前的状态。
41、封装过 axios 吗?看过 axios 源码吗?
42、http 请求头有哪些?
43、介绍一下 Fiber 的中断和恢复?
-
Fiber 的工作原理:Fiber 是 React 的内部架构,用于管理和执行渲染任务。Fiber 是一个任务单元,每个 Fiber 节点代表一个组件或一个工作单元。React Fiber 将渲染任务分解为多个小的任务单元,这些任务单元可以被暂停、恢复或重新安排。
-
Fiber 两个阶段:
- Reconciliation 阶段:在这个阶段,React Fiber 会找出需要更新哪些 DOM;该阶段可以被打断;
- Commit 阶段:在这个阶段,React 会根据 Effect List 将所有变更一次性更新到 DOM 上;这个阶段无法被打断。
- 实现机制:
- 任务分割:React Fiber 将渲染任务分割成多个小的任务单元(Fibers)。每个 Fiber 节点代表一个组件或一个工作单元。这种任务分割使得 React 可以在渲染过程中暂停和恢复任务。
- 任务优先级:React Fiber 引入了任务优先级机制,允许 React 根据任务的紧急程度来调度任务。高优先级的任务(如用户输入或动画)可以中断低优先级的任务(如后台数据加载或离屏渲染)。
- 时间切片:React Fiber 使用时间切片(Time Slicing)技术来实现任务的中断和恢复。在每个时间片内,React 会执行一部分任务,然后检查是否有更高优先级的任务需要处理。如果没有,继续执行当前任务;如果有,中断当前任务,处理更高优先级任务。
- 中断和恢复的具体流程:
- 任务中断: (1)当高优先级任务需要执行时,当前的低优先级任务会被中断; (2)React 会将当前任务的状态保存到全局变量中; (3)中断时会刷新栈帧,以便开始处理高优先级的任务。
- 任务恢复: (1)高优先级任务执行完毕后,React 会恢复之前中断的任务; (2)React 通过保存的全局变量重新设置执行点,将中断时的上下文重新加载到栈帧中; (3)从中断点继续执行 performUnitOfWork,完成剩余的任务。
- 中断的流程:
- 中断条件:以下情况中断当前任务:高优先级任务到来:当一个高优先级任务(如用户输入或动画)到来时,React 会中断当前的低优先级任务;时间片用完:当当前时间片用完时,React 会检查是否有更高优先级的任务需要处理。
- 保存状态:当中断发生时,React 会保存当前任务的状态,这包括:当前 Fiber 节点:保存当前正在处理的 Fiber 节点;工作进度:保存当前任务的执行进度;Effect List:保存当前任务的副作用列表。
- 刷新栈帧:React 会刷新当前栈帧,以便开始处理高优先级任务。这包括:清空当前栈帧:清空当前正在执行的任务的栈帧;设置新的栈帧:为高优先级任务设置新的栈帧。
- 恢复的流程:
- 检查中断任务:当高优先级任务处理完毕后,React 会检查是否有被中断的任务需要恢复。如果有,React 会恢复被中断的任务。
- 加载状态:加载之前保存的当前状态,包括:当前 Fiber 节点:恢复当前正在处理的 Fiber 节点;工作进度:恢复当前任务的执行进度;Effect List:恢复当前任务的副作用列表。
- 继续执行:React 会从中断点继续执行任务,直到任务完成或再次被中断。
44、如何检测是否出现循环引用?
- 使用 Set:
function hasCircularReference(obj, seen = new Set()) {
if (seen.has(obj)) {
return true; // 如果对象已经在集合中,说明存在循环引用
}
seen.add(obj); // 将当前对象添加到集合中
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (value && typeof value === 'object') { // 如果值是对象或数组
if (hasCircularReference(value, seen)) { // 递归检查
return true;
}
}
}
}
return false; // 如果遍历完所有属性都没有发现循环引用,则返回 false
}
2. 使用 WeakSet:
function hasCircularReference(obj, seen = new WeakSet()) {
if (seen.has(obj)) {
return true; // 如果对象已经在 WeakSet 中,说明存在循环引用
}
seen.add(obj); // 将当前对象添加到 WeakSet 中
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (value && typeof value === 'object') { // 如果值是对象或数组
if (hasCircularReference(value, seen)) { // 递归检查
return true;
}
}
}
}
return false; // 如果遍历完所有属性都没有发现循环引用,则返回 false
}
3. 使用 JSON.stringify 检测循环引用:
function hasCircularReference(obj) {
try {
JSON.stringify(obj);
return false; // 如果没有抛出错误,说明没有循环引用
} catch (e) {
return true; // 如果抛出错误,说明存在循环引用
}
}
4. 使用 Proxy 检测循环引用:
function hasCircularReference(obj) {
const seen = new Set();
const handler = {
get(target, prop) {
if (seen.has(target)) {
throw new Error('Circular reference detected');
}
seen.add(target);
const value = target[prop];
if (value && typeof value === 'object') {
return new Proxy(value, handler); // 递归创建 Proxy
}
return value;
}
};
try {
new Proxy(obj, handler);
return false; // 如果没有抛出错误,说明没有循环引用
} catch (e) {
return true; // 如果抛出错误,说明存在循环引用
}
}
45、Webpack 的 runtime 做什么?
__webpack_modules__: 维护一个所有模块的数组。将入口模块解析为 AST,根据 AST 深度优先搜索所有的模块,并构建出这个模块数组。每个模块都由一个包裹函数(module, module.exports, __webpack_require__)对模块进行包裹构成。__webpack_require__(moduleId): 手动实现加载一个模块。对已加载过的模块进行缓存,对未加载过的模块,执行 id 定位到__webpack_modules__中的包裹函数,执行并返回module.exports,并缓存。__webpack_require__(0): 运行第一个模块,即运行入口模块。
另外,当涉及到多个 chunk 的打包方式中,比如 code spliting,webpack 中会有 jsonp 加载 chunk 的运行时代码。
46、Pinia 和 Redux 的区别是什么?
Redux:Redux 是一个基于 Flux 架构的预测性状态管理库,最初是为 React 设计的,但也可以与其他前端框架或库一起使用。Redux 的核心设计理念是将应用的状态存储在一个集中化的存储(store)中,并通过一系列的规则来管理状态的更新。
- 单一数据源:整个应用的状态存储在一个对象树(state tree)中;
- 状态是只读的:状态不能直接修改,而是通过触发“动作”(actions)来描述发生了什么;
- 使用纯函数来执行状态更新:状态的更新由“Reducer”函数负责,它是一个纯函数,它接收当前的状态和一个动作,返回新的状态。
Pinia:简化状态管理,提供更直观的 API 和更灵活的使用方式。
- 模块化设计:Pinia 的状态管理是模块化的,每个模块可以独立定义自己的状态、动作和 getters。
- 简洁的 API:Pinia 提供了更简洁的 API,减少了样板代码。
- 与 Vue.js 深度集成:Pinia 与 Vue.js 的生命周期和响应式系统深度集成,使得状态管理更加自然和高效。
总结:
- Redux:适用于需要高度可扩展性和复杂状态管理的大型应用,尤其是在 React 生态系统中。Redux 提供了强大的功能和丰富的生态系统,但需要一定的学习成本。
- Pinia:适用于 Vue.js 应用,提供了简洁的 API 和模块化设计,使得状态管理更加直观和高效。Pinia 与 Vue.js 的深度集成使得其在 Vue.js 生态系统中表现优异。
47、详述前端打包的原理和流程细节,包括 webpack 和 vite。
前端打包是将开发环境中的代码、样式、图片等资源转换为生产环境中可以直接部署的静态文件的过程。这个过程包括代码的压缩、拆分、优化等多个步骤。
Webpack:
核心思想:是一个模块打包工具,它将项目中的所有资源(JS、CSS、图片等)视为模块,通过依赖关系构建依赖图,借助 Loader 转换,借助 plugin 插件进行优化,最终打包成一个或多个静态文件。
打包原理:
- 依赖图构建:将项目中的所有资源(JS、CSS、图片等)视为模块,通过依赖关系构建依赖图。它会从配置的入口文件开始,递归地找出所有依赖的模块,并将它们打包成一个或多个 bundle。
- 模块转换:使用 Loader 来处理非 JS 文件,如 CSS、图片等。Loader 可以将这些文件转换成 JS 模块,以便 Webpack 能够处理。
- 插件扩展:插件系统允许在打包过程中执行各种任务,如优化代码、压缩、生成资源文件等。插件可以在 Webpack 的生命周期中监听特定的事件,并执行相应的逻辑。如 HtmlWebpackPlugin 可以在打包完成后生成 HTML 文件。
- 代码优化:使用插件对代码进行优化,如压缩、代码分割、摇树优化等。这些优化步骤通过插件实现。
打包流程:
- 初始化参数:从配置文件(如 webpack.config.js)和命令参数中读取配置信息,合并为最终的配置对象。
- 初始化 Compiler:使用配置对象初始化 Compiler 对象,加载所有配置的插件。
- 确定入口:根据配置中的 entry 属性找到入口文件。
- 编译模块:从入口文件开始,递归解析所有依赖的模块。对于每个模块,有如下步骤:
- 加载模块:如果模块是 JS 文件,直接加载;如果是其他类型的文件,使用对应的 Loader 进行转换。
- 解析依赖:解析模块中的 import、require 等语句,找到依赖的模块。
- 递归处理:对依赖的模块重复上述步骤,直到所有模块都被处理。
- 构建依赖图:将所有模块及其依赖关系构建为一个依赖图。依赖图的每个节点代表一个模块,边代表模块之间的依赖关系。
- 优化模块:对模块进行优化,包括代码分割、摇树优化、压缩等。这些优化步骤通过插件实现。
- 输出资源:根据依赖图,将所有模块打包成一个或多个 bundle 文件。每个 bundle 文件包含多个模块的代码。
- 写入文件系统:将打包后的文件写入到配置的输出目录中。
Vite:
核心思想:利用了浏览器原生的 ES Module 支持,实现了快速的开发体验和高效的打包过程。
打包原理:
- 利用浏览器原生的 ESM 支持:核心原理是利用浏览器对 ESM 的原生支持。当运行 Vite 项目时,Vite 会启动一个开发服务器,并在浏览器中动态加载模块。这种方式避免了传统打包工具(如 Webpack)在启动时对所有模块进行预编译的开销。
- 依赖预构建:在开发模式下,Vite 会在启动时对项目中的依赖进行预构建。具体来说,Vite 使用 esbuild 对 node_modules 中的依赖进行快速转换,将它们打包成少数几个文件,以减少浏览器的请求次数。预构建的结果会被缓存到 node_modules/.vite 目录中,只有在依赖发生变化时才会重新构建。
- 按需编译:Vite 会在浏览器请求模块时进行按需编译。当浏览器通过 import 加载模块时,Vite 会拦截这些请求,并在服务器对模块进行编译和转换,然后将结果返回给浏览器。这种方式使得 Vite 能够实现真正的按需加载,提高开发效率。
打包流程:
- 开发环境:
- 启动开发服务器:运行
npm run dev或类似的命令启动 Vite 开发服务器。 - 依赖预构建:使用 esbuild 对项目中的依赖进行预构建,将它们转换为 ESM 格式。
- 按需编译:当浏览器请求模块时,Vite 会拦截这些请求,并对模块进行即时编译。
- 热模块替换(HMR):支持热模块替换,允许开发者在不刷新页面的情况下实时查看代码更改。
- 生产环境:
- 构建应用程序:运行
npm run build或类似的命令启动构建过程。 - 使用 Rollup 打包:在生产环境使用 Rollup 打包,它会将项目中的所有模块打包成一个或多个 bundle 文件。
- 代码压缩和优化:Rollup 会对代码进行压缩和优化,如删除未使用代码、合并模块等。
- 生成静态资源:打包完成后,Vite 会生成最终的静态资源文件,包括 JS 文件、CSS 文件和其他静态文件。
48、关于前端打包中产生的哈希码。
哈希码原理:
哈希码是一种将任意长度的数据转换为固定长度的字符串的过程。哈希函数具有以下特性:
- 确定性:相同的输入总是产生相同的哈希值。
- 唯一性:不同的输入产生不同的哈希值(虽然理论上存在哈希碰撞,但在实际应用中可以忽略)。
- 不可逆性:无法从哈希值反推出原始数据。
在前端打包中,哈希码通常用于文件名或文件内容中,以确保文件的唯一性。
哈希码生成方式:
- 基于文件内容生成,任何文件内容变化,哈希码变化;
- 基于整个编译过程生成,编译过程中的任何内容发生变化,就会变化;
- 基于模块的标识符生成,模块的标识符发生变化,则变化。
哈希码的作用:
- 避免浏览器缓存:浏览器会缓存静态资源文件,以提高加载速度。如果文件名不变,浏览器可能会加载旧的缓存文件,导致用户看不到最新的内容。通过在文件名中加入哈希码,可以确保浏览器加载最新的文件。
- 确保文件的唯一性:可以确保每个文件在内容发生变化时都有一个唯一的标识符,从而避免文件名冲突。
- 优化缓存策略:通过合理使用,可以实现长期缓存和短期缓存的结合。如将公共模块设为长期,动态内容设为短期。
49、如何解决前端版本更新,页面不自动刷新问题?
50、ref 和 reactive 的区别是什么?
ref:用于创建一个响应式的引用对象,通常用于包装基本数据类型(如数字、字符串、布尔值等)或 DOM 元素。
reactive:用于创建一个响应式的对象,通常用于包装复杂的数据类型(如对象、数组),会递归地将对象的属性转为响应式。
二者区别:
- 数据类型:
ref用于包装基本数据类型或 DOM 元素。reactive用于包装复杂数据类型(对象、数组)。
- 访问方式:
ref需要通过.value属性访问或修改值。reactive直接操作对象的属性。
- 使用场景:
ref适用于简单的响应式数据或 DOM 引用。reactive适用于复杂的响应式数据结构。
51、React 的设计思路和运行流程。
设计思路
- 声明式编程:采用声明式编程范式,开发者只需描述 UI 的最终状态,React 会自动处理从当前状态到新状态的转换。这种方式使得代码更加直观和易于维护。
- 组件化:React 将 UI 分解为独立的、可复用的组件。每个组件负责渲染一部分 UI,并且可以通过属性(props)和状态(state)来控制其行为。
- 单向数据流:React 采用单向数据流,数据从父组件流向子组件,通过 props 传递。如果需要修改数据,通常通过回调函数或状态管理库(如 Redux 或 Context API)来实现。
- 虚拟 DOM:React 使用虚拟 DOM 来提高性能。当组件的状态或属性发生变化时,React 会重新渲染虚拟 DOM,然后通过算法计算出实际 DOM 的最小变化,并应用到这些变化的真实 DOM 中。
运行流程
- 初始化:当 React 应用启动时,React 会解析组件树,构建虚拟 DOM,并将其渲染到页面上。
- 状态更新:当组件的状态(state)或属性(props)发生变化时,React 会重新渲染组件。这个过程包括以下步骤:
(1)调用 setState 或 useState:触发状态更新。
(2)重新渲染组件:React 会重新调用组件的渲染函数,生成新的虚拟 DOM。
(3)比较虚拟 DOM:React 会比较新旧虚拟 DOM 的差异。
(4)更新真实 DOM:React 会根据差异更新真实 DOM。
- 生命周期:
- 挂载阶段:组件被创建并插入到 DOM 中。
- 更新阶段:组件的状态或属性发生变化,导致组件重新渲染。
- 卸载阶段:组件从 DOM 中移除。
- 高阶组件和 hooks:React 提供了高阶组件(HOC)和 Hooks 等机制,用于复用组件逻辑和状态管理。Hooks 是 React 16.8 引入的新特性,允许在函数组件中使用状态和其他 React 特性。
52、React 19 中的新特性。
53、Lightinghouse 中的前端性能指标有哪些?
- First Contentful Paint (FCP):用于衡量浏览器在用户导航到网页后渲染第一部分 DOM 内容所需的时间。网页上的图片、非白色
<canvas>元素和 SVG 会被视为 DOM 内容。不包括 IFrame 中的任何内容。(1.2s 对应 99%)。 - 速度指数(Speed Index):用于衡量网页加载期间内容可视化显示的速度。Lighthouse 首先会捕获浏览器中网页加载的视频,并计算帧之间的视觉进度。然后,Lighthouse 使用 Speedline Node.js 模块生成速度指数得分。
- Total Blocking Time(TBT):用于衡量网页被阻止响应用户输入(例如鼠标点击、屏幕点按或键盘按键的总时间)。计算方法是将 First Contentful Paint 和 Time to Interactive 之间的所有长任务的阻塞部分相加。任何执行时间超过 50ms 的任务都是长任务。50ms 后的时间就是阻塞部分。例如,如果 Lighthouse 检测到一个时长为 70 毫秒的任务,则阻塞部分将为 20 毫秒。
- Largest Contentful Paint (LCP):LCP 衡量的是视口中最大内容元素何时渲染到屏幕上。这大致表示网页的主要内容何时可供用户查看。如需详细了解如何确定 LCP,请参阅“最大内容渲染时间”定义。
- Cumulative Layout Shift (CLS):用于衡量在网页的整个生命周期内发生的每一次意外布局偏移的布局偏移得分的最高偏移分数。
54、前端如何实现并发请求数量控制?
// 并发请求函数
const concurrencyRequest = (urls, maxNum) => {
return new Promise((resolve) => {
if (urls.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 下一个请求的下标
let count = 0; // 当前请求完成的数量
// 发送请求
async function request() {
if (index === urls.length) return;
const i = index; // 保存序号,使result和urls相对应
const url = urls[index];
index++;
console.log(url);
try {
const resp = await fetch(url);
// resp 加入到results
results[i] = resp;
} catch (err) {
// err 加入到results
results[i] = err;
} finally {
count++;
// 判断是否所有的请求都已完成
if (count === urls.length) {
console.log('完成了');
resolve(results);
}
request();
}
}
// maxNum和urls.length取最小进行调用
const times = Math.min(maxNum, urls.length);
for(let i = 0; i < times; i++) {
request();
}
})
}
55、computed vs useMemo & watch vs useEffect?
56、SEO 的原理是什么?
原理:SEO(Search Engine Optimization,搜索引擎优化)是一种通过优化网站内容和结构,提升网站在搜索引擎结果页面(SERP)中排名的技术。
- 搜索引擎工作原理:
- 爬取:搜索引擎使用爬虫来浏览网上的网页。爬虫会从一个网页跳转到另一个网页,通过链接发现新的内容。
- 索引:搜索引擎会将爬取到的网页内容存储到索引中。索引是一个巨大的数据库,包括网页的标题、内容、链接等信息。
- 排名:当用户输入搜索查询时,搜索引擎会从索引中检索相关网页,并根据一系列算法对这些网页进行排名,以确定哪些网页最符合用户的查询意图。
- SEO 的核心要素:
- 内容质量:高质量的内容是 SEO 的基础。搜索引擎倾向于推荐有价值、准确且友好的内容。
- 关键词优化:即用户在搜索引擎中输入的词语。通过在内容中合理使用关键词,可以提高网页在搜索结果中的排名。
- 技术优化:确保网站的技术性能良好,如页面加载速度、移动友好性、HTTPS 等,可以提升用户体验,从而提高排名。
- 用户体验:提供良好的用户体验,如清晰的导航、快速的加载时间和高质量的内容,可以提高用户满意度,进而提升网站在搜索结果中的排名。
- 反向链接:指其他网站链接到你的网站。高质量的反向链接可以提高网站的权威性能,从而提升排名。
- SEO 的具体方法:
- 关键词研究:通过工具(如 Google Keyword Planner、SEMrush)分析目标关键词,了解用户搜索行为和竞争情况。
- 内容优化:在标题、元描述、正文等位置合理使用关键词,同时确保内容质量高、有价值。
- 技术优化:优化网站的加载速度,确保移动友好性,使用 HTTPS 等技术手段提升用户体验。
- 反向链接建设:通过高质量的内容吸引其他网站的链接,提高网站的权威性。
- 社交媒体营销:利用社交媒体平台推广内容,增加网站的曝光率和流量。
57、拖拽如何实现?直接原生怎么写?有哪些插件可以借用?
- 原生 HTML5 拖拽 API:
- 设置可拖拽元素:在 HTML 中设置元素的
draggable属性为true。 - 定义拖拽事件处理函数:实现
dragstart、dragover、drop等事件处理函数。 - 处理拖拽数据:通过
DataTransfer对象在拖拽过程中传递数据。
-
使用 JS 和 CSS:
通常需要手动处理鼠标事件(如
mousedown、mousemove和mouseup)。 -
使用第三方库:
- SortableJS:SortableJS 是一款强大的开源 JavaScript 库,无需依赖 jQuery 或特定框架,就能在现代浏览器和触摸设备上实现可重新排序的拖放列表。
- Interact.js:Interact.js 支持主流浏览器的调整大小和多点触控手势,提供简单灵活的 API,可完全掌控库的行为。
- Dragula:Dragula 提供简单易用的 API,能轻松在应用中部署拖放功能,支持主流浏览器和 IE7+。
- Vue Draggable:Vue Draggable 是一个基于 Vue.js 的拖拽组件,支持 Vue 2 和 Vue 3。
- XYFlow:是一个强大的开源库,用于构建基于节点的用户界面(UI)。它支持 React 和 Svelte 框架,提供了丰富的功能和高度可定制性。
58、说一说你在前端开发中用过的安全策略。
- 输入验证
- 客户端验证:使用 HTML5 的表单验证属性(如
required、pattern)和 JavaScript 来验证用户输入。 - 正则表达式:使用正则表达式来验证输入数据的格式,如邮箱、电话等。
- 长度限制:限制输入数据的长度,防止缓冲区溢出攻击。
- 使用第三方库
- 选择知名库:优先选择社区活跃、维护良好的知名库。如下:
(1)CryptoJS:
CryptoJS 是一个广泛使用的 JavaScript 加密库,提供了多种高效的加密算法和便捷的接口。它可以帮助你实现数据的加密和解密,确保数据在传输过程中的安全性。
(2)jwt-decode:
jwt-decode 是一个用于解码 JWT(JSON Web Token)的 JavaScript 库。它可以帮助你在浏览器环境中解码 Base64Url 编码的 JWT 令牌,从而获取其中的有效载荷信息。这对于验证用户身份和保护敏感信息非常有用。
(3)js-cookie:
js-cookie 是一个简单、轻量级的处理 cookies 的 JavaScript API。它可以帮助你安全地设置、获取和删除 cookies,从而管理用户的会话和身份验证信息。
(4)Husky:
Husky 是一个流行的 Node.js 工具,用于在特定 Git 操作(如提交、推送等)发生时自动触发脚本。这些脚本可以用于执行各种任务,比如代码格式化、质量检查、单元测试等,从而帮助团队保持一致的代码质量和开发流程。这有助于防止代码提交中的安全问题。
(5)ESLint:
ESLint 是一个用于识别和报告 JavaScript 代码中问题的工具。它帮助开发者保持代码的一致性和高质量,通过强制执行一套编码规范来避免潜在的错误。这有助于防止代码中的安全问题。
- 定期更新:保持第三方库的最新版本,及时应用安全补丁。
- 审查库代码:在使用之前审查库的代码和文档,确保其安全性。
- 避免暴露敏感信息
- 环境变量:使用环境变量存储敏感信息,在构建过程中注入前端代码。
- 后端处理:将敏感操作和数据处理放在后端,前端只负责与后端交互。
- 加密:对需要传输的敏感数据进行加密处理。
- 采用安全的 HTTP 头
- Content Security Policy (CSP) :限制资源加载来源,防止 XSS 攻击。
- X-Content-Type-Options:防止浏览器对 MIME 类型的猜测,确保文件类型正确。
- X-Frame-Options:防止点击劫持攻击,禁止网页被嵌入到 iframe 中。
- 避免跨站脚本攻击(XSS)
- 输出编码:对所有输出到 HTML 的内容进行编码处理,防止恶意脚本执行。
- 输入过滤:对用户输入的数据进行过滤,移除不安全的字符。
- 使用安全 API:使用安全的 API,如
textContent而不是innerHTML,避免动态插入 HTML。
- 避免跨站请求伪造(CSRF)
- CSRF Token:在表单中添加 CSRF Token,并在服务器端验证。
- Referer 验证:检查 HTTP Referer 头,确保请求来自合法页面。
- 避免点击劫持
- X-Frame-Options:设置 HTTP 头
X-Frame-Options: DENY,防止页面被嵌入 iframe。 - Content-Security-Policy:设置
Content-Security-Policy: frame-ancestors 'self',进一步防止点击劫持。
- 使用 HTTPS: 启用 HTTPS 协议,确保数据传输的安全性。可以通过 Let’s Encrypt 等免费证书服务获取 SSL 证书。
- 定期更新依赖库:
及时更新项目中使用的第三方库,修复已知的安全漏洞。可以使用工具如
npm audit或yarn audit来检查和更新依赖库。 - 安全审计: 定期进行安全审计,发现并修复潜在的安全问题。可以使用静态代码分析工具如 ESLint、SonarQube 等,以及动态扫描工具如 OWASP ZAP、Burp Suite 等。
- 用户教育: 教育用户不要点击不明链接,不要在不安全的网络环境中登录账户。可以通过用户手册、帮助文档等方式向用户普及安全知识。
- 日志记录和监控: 记录关键操作的日志,并设置监控报警,及时发现和响应异常行为。可以使用 ELK Stack(Elasticsearch, Logstash, Kibana)等工具进行日志管理和分析。
- 权限管理: 实施最小权限原则,确保每个用户和组件只拥有完成任务所需的最低权限。使用 RBAC(基于角色的访问控制)或 ABAC(基于属性的访问控制)等权限管理模型。
- 数据加密: 对敏感数据进行加密存储和传输,防止数据泄露。可以使用 AES、RSA 等加密算法,以及 OpenSSL 等工具进行数据加密。
59、promise 是如何链式调用的?
Promise 的链式调用是通过 .then() 方法实现。每个 .then() 方法返回一个新的 Promise 实例,从而允许继续在新的 Promise 上调用 .then() 或 .catch() 方法。
.then() 方法的工作原理:
- 接收两个参数:
onFulfilled:当 Promise 成功时调用的回调函数。onRejected:当 Promise 失败时调用的回调函数(可选)。
当 .then() 被调用时,它会返回一个新的 Promise 实例。这个新的 Promise 的状态取决于 onFulfilled 或 onRejected 回调函数的返回值。
60、写一个 Promise,实现状态扭转。
61、有哪些方案实现图片的懒加载?
(1)使用 Intersection Observer API
Intersection Observer 是一个现代的浏览器 API,用于检测元素是否进入视口。它是实现懒加载的推荐方法,因为它性能高效,不需要频繁地计算元素的位置。
(2)使用事件监听和滚动事件
在不支持 Intersection Observer 的浏览器中,可以使用传统的事件监听和滚动事件来实现懒加载。这种方法需要在每次滚动时计算图片的位置,性能相对较低。
(3)使用第三方库
- Lazysizes:一个高性能的懒加载库,支持图片、视频等多种资源的懒加载。
- Lozad.js:一个轻量级的懒加载库,基于
Intersection ObserverAPI 实现。
(4)使用 HTML 的 loading="lazy" 属性
现代浏览器支持在 <img> 和 <iframe> 标签中使用 loading="lazy" 属性,实现原生的懒加载功能。
61、原型的机制是什么?
通过原型链实现对象的继承和属性共享。
62、对于 SSR 渲染,若整个页面内容很多,接口也很慢,如何进一步加快 SSR 的首屏渲染?
63、如何缩小文字小于 12px ?
- zoom:字面意思是“变焦”,可以改变页面上元素的尺寸,属于真实尺寸。
- -webkit-transform:scale():针对
chrome浏览器,加webkit前缀,用transform:scale()这个属性进行放缩。注意的是,使用scale属性只对可以定义宽高的元素生效,所以,下面代码中将span元素转为行内块元素 - -webkit-text-size-adjust:该属性用来设定文字大小是否根据设备(浏览器)来自动调整显示大小。属性值有:percentage:字体显示的大小;auto:默认,字体大小会根据设备/浏览器来自动调整;none:字体大小不会自动调整。
64、组件化有哪些目的?(尽可能多)
前端组件化是现代前端开发中的一种重要实践,它将用户界面拆分成独立、可复用的组件。以下是组件化在前端开发中的重要性和必要性:
1. 提高代码复用性
- 减少重复代码: 组件化允许开发者将重复的 UI 元素封装成独立的组件,从而减少重复代码。例如,一个按钮组件可以在多个页面中复用,而无需重复编写代码。
- 提高开发效率: 通过复用组件,开发者可以更快地构建新的功能,而无需从头开始编写代码。这显著提高了开发效率,特别是在大型项目中。
2. 提升代码可维护性
- 独立性: 每个组件都是独立的,具有自己的逻辑和样式。这使得开发者可以独立地修改和维护组件,而不会影响其他部分。
- 模块化: 组件化促进了模块化开发,使得代码结构更加清晰,易于理解和维护。开发者可以专注于单个组件的开发,而无需了解整个项目的复杂性。
3. 促进团队协作
- 分工明确: 在大型团队中,组件化允许不同的开发者或团队独立开发和维护不同的组件。这减少了开发过程中的冲突,提高了团队协作效率。
- 代码共享: 组件化使得团队成员可以共享和复用组件,减少了重复工作。这有助于团队更快地交付高质量的产品。
4. 提高用户体验
- 一致性: 组件化确保了 UI 元素在不同页面和功能中的行为和外观保持一致。这有助于提供更一致的用户体验。
- 性能优化: 通过按需加载组件,可以减少初始加载时间,提高页面性能。这有助于提供更流畅的用户体验。
5. 适应变化
- 灵活性: 组件化使得开发者可以快速适应需求变化。如果需要修改某个 UI 元素,只需修改对应的组件即可,而无需修改多个地方。
- 可扩展性: 组件化支持项目的可扩展性。随着项目的发展,可以轻松地添加新的组件或修改现有组件,而无需重构整个项目。
6. 总结
组件化是现代前端开发的核心实践,它通过提高代码复用性、提升代码可维护性、促进团队协作、提高用户体验和适应变化,显著提升了前端开发的效率和质量。通过将用户界面拆分成独立、可复用的组件,开发者可以更高效地构建和维护大型前端项目。
65、了解 SSE 吗?和 Websocket 比有哪些异同?
66、jsx 是通过什么工具去编译?编译之后是什么结构?
工具:
Babel 是编译JSX的核心工具。它将JSX语法转换为普通的JavaScript代码。为了使用Babel,你需要安装一些必要的依赖包(包括@babel/core、@babel/preset-env和@babel/preset-react),并进行简单的配置。
编译过程:
- 词法分析:将 JSX 代码拆分为 Token 序列。
- 语法分析:将 Token 序列转换为抽象语法树(AST)。
- 代码生成:将 AST 转换成 JavaScript 代码。
编译后的结构:
JSX 代码会被编译成 React.createElement 的调用。React.createElement 是一个函数,用于创建 React 元素。它接受三个参数:
- 类型:可以是一个字符串(表示 HTML 标签)或一个 React 组件。
- 属性:一个对象,包含元素的属性。
- 子元素:可以是一个字符串、一个 React 元素或一个子元素数组。
67、你觉得渐进式是指什么意思?
渐进式框架的含义:
渐进式框架(Progressive Framework)是一种设计理念,指的是框架可以逐步引入和使用,开发者可以根据需要选择性地使用框架的某些部分,而不需要一次性采用整个框架。这种设计理念使得框架更加灵活,适合不同规模和复杂度的项目。
渐进式框架的优势:
1. 灵活性: 渐进式框架允许开发者根据项目的具体需求选择性地使用框架的功能,而不需要一次性采用整个框架。这种灵活性使得框架可以适应不同规模和复杂度的项目。 2. 可扩展性: 渐进式框架提供了从简单到复杂的多种使用方式,开发者可以根据需要逐步引入更多的功能和复杂性。这种可扩展性使得框架可以从小型项目扩展到大型项目。 3. 易于上手: 渐进式框架通常提供了简单的入门方式,开发者可以快速上手并逐步学习更多的功能。这种易于上手的特性使得框架适合初学者和经验丰富的开发者
68、JS 的继承有哪些?那些场景用继承比较好?
69、详解 defineProperty。
70、Vue3 原理解析系列。
71、nodejs 面试题。
72、简历中提到 Redis 和 indexedDB。
Redis:使用一个计数器存储当前请求量(每次使用 incr 方法相加),并设置一个过期时间,计数器在一定时间内自动清零,从而实现限流。步骤如下:
- 使用 Redis 的计数器保存当前请求的数量。
- 设置一个过期时间,使得计数器在一定时间内自动清零。
- 每次收到请求时,检查计数器当前值,如果未达到限流阈值,则增加计数器的值,否则拒绝请求。
IndexedDB:保存用户拥有的 token 列表数据。
73、路由懒加载的原理是什么?
在 Vue 中,传统的路由加载方式是将所有组件打包到一个JS文件中,当用户访问应用时,所有组件一次性加载。这种方式在应用规模较小时尚可接受,但面对大型应用,会显著增加应用的加载时间。而路由懒加载则通过动态导入的方式,仅在用户访问特定路由时才加载对应的组件。这种方式不仅减少了初始加载时间,还优化了资源的利用。
原理:
- 代码分割:代码分割是懒加载的基础,通过将应用的代码分割成多个小包,可以在需要时动态加载它们。可用 import() 实现。
- 按需加载:指当用户访问某个路由时,才会加载对应的组件。可用路由守卫如 beforeEnter 实现。
- 异步组件:在需要时才加载的组件,配合 import() 语法。
- 动态 import:可实现动态加载模块。
优化策略:
- 使用Webpack的代码分割功能:
Webpack提供了强大的代码分割功能,可以自动对动态导入的模块进行分割。通过配置Webpack的
splitChunks参数,可以进一步优化代码分割的效果。 - 异步组件加载优化:
为了避免重复加载已经加载过的组件,可以使用缓存策略。此外,利用
webpackPrefetch和webpackPreload可以提前加载可能需要的模块,减少未来访问时的等待时间。 - 延迟加载策略: 根据用户的行为,如滚动、点击等,可以延迟加载非关键组件。这种策略可以在不影响用户体验的前提下,进一步减少初始加载时间。
74、v-if 和 v-for 分别在 vue2 和 vue3 中的优先级是怎样?
75、script 标签是放在 head 还是 body 合适?
JS 若放在 head 内会阻塞页面的传输和渲染。当然,外部 JS 还是一般放在 head 内。而内部的本页面 JS 脚本则一般放在 body 内,好处有以下几点:
- 不阻塞页面的加载(事实上 js 会被缓存);
- 可以直接在 js 里操作 dom,这时候 dom 是准备好的,即保证 js 运行时 dom 是存在的;
- 放在页面底部,监听 window.onload 或 readystate 来触发 js。
76、如何编写一个 webpack 插件?
一个简单的插件结构如下:
class ExamplePlugin {
constructor(options) {}
apply(compiler) {
compiler.hooks.emit.tapAsync("ExamplePlugin", (compilation, callback) => {
console.log("This is an ExamplePlugin")
callback();
})
}
}
在使用这个插件时,在 webpack 中的配置如下:
const ExamplePlugin = require('./ExamplePlugin.js');
module.exports = {
plugins: [
new ExamplePlugin(options)
]
}
Webpack 插件的组成:
- 一个 ES6 class 类;
- 在类中定义一个 apply 方法;
- 指定一个绑定到 webpack 自身的事件钩子;
- 处理 webpack 内部实例的特定数据;
- 功能完成后调用 webpack 提供的回调。
77、Vue3 patch 更新组件的逻辑。
- 如果新旧 VNode 节点是同一个,则直接返回不做处理;
- 如果新旧 VNode 的节点类型不同,那就将旧的 VNode 节点卸载,然后将旧的 VNode 节点置空,最后走挂在逻辑;
- 如果新旧 VNode 节点的类型相同,会根据不同的 VNode 类型走不同的更新逻辑,譬如组件走 processComponent 流程,普通 DOM 元素节点走 processElement 流程。
78、vue-router 中有哪些导航守卫?
- 全局前置守卫:
router.beforeEach - 全局解析守卫:
router.beforeResolve - 全局后置钩子:
router.afterEach - 路由独享的守卫:
beforeEnter - 组件内的守卫:
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave