know

笔记

# 1. 谈谈你的职业规划

  1. 维护工作与生活的平衡 1.1 确保工作与生活的平衡,注重身体健康和心理健康。 1.2 学会管理时间,高效率地完成任务。
  2. 分享教育他人,更新博客,参与开源
  3. 持续学习,关注并学习新技术
  4. 多维度技能
  5. 建立良好的人际关系,扩宽视野,与同行互动

# 2. 项目文档一般都要记录哪些基本的东西?

  1. 项目概述和目标

该部分描述项目的背景,目的,目标,范围以及预期结果。它可以帮助参与者了解项目的重要性和在完成项目后将实现什么。

  1. 项目计划和时间表

该部分包括项目的时间表以及每个阶段的关键日期。它应该显示出明确的里程碑,以便主要决策者,项目经理和其他利益相关者进行跟踪和监视。

  1. 需求和功能

该部分详细描述产品的功能和需求,包括产品的整体目标,重要特性和用户故事等。

  1. 设计和架构

该部分定义产品的体系结构,包括数据结构,程序流程图和技术堆栈等。

  1. 测试和质量保证

该部分概述产品的质量保证和测试计划,包括如何测试,测试环境,如何记录和跟踪缺陷等。

  1. 部署和维护

该部分描述产品部署的计划,包括硬件和软件要求,并提供操作手册和维护指南。

  1. 风险和机会管理

该部分识别和管理项目相关的所有风险和机会。这有助于建立明确的业务对策和计划,以避免或缩减风险和利用机会。

  1. 人员组成与角色职责

该部分定义参与项目的人员组成及各个角色的职责范围,以确保在项目管理和执行过程中能够实现良好的协作和沟通。

# 3. 聊聊最复杂的业务如何处理的

  1. 创造明确的计划和目标
  2. 与客户和业务用户进行充分的沟通。了解业务用户的需求和期望,并与客户协商和确定项目的基本参数和要求。
  3. 对项目进行分类管理。按照具有相似特征和类型的业务逻辑分组,分别进行管理。
  4. 随时进行风险评估和改进策略和方案
  5. 鼓励协作和团队工作
  6. 采用现代的技术和工具
  7. 收集并利用数据
  8. 培养项目管理和技术管理能力

# 4. 带团队中有哪些难点

  1. 沟通
  2. 策略和决策
  3. 团队合作
  4. 员工招聘和培训
  5. 时间和工作负载
  6. 人格差异和冲突
  7. 技术变革

# 5. 聊聊迭代流程

  1. 规划阶段
  2. 详细设计&开发阶段
  3. 测试&集成阶段
  4. 发布阶段

# 6. 首屏优化的方案,分别从代码、网络和缓存说一下。

  1. 代码方面:
  • 使用懒加载技术,将首屏不必要的内容放到后面加载。
  • 删除不必要的代码,尤其是第三方库,在优化性能足够大的时候可以考虑更改引入库的方式,以最大程度减小文件体积。
  • 使用字体图标代替图片,减少 http 请求次数,降低时间消耗。
  1. 网络方面:
  • 压缩代码,减少文件大小
  • 使用 CDN 加速
  • 使用 HTTP/2 协议,多路复用能够减少 socket 连接
  • 开启缓存,使用 immutable 缓存机制,避免多余请求的发生,提高网站访问速度。
  • 数据传递时使用二进制格式(如 protobuf)替代 JSON 等格式,可以减少请求资源大小,加快数据传输速度。
  • 异步加载,尽可能推迟 JS 的加载和执行时机。
  1. 缓存方面:
  • 利用 Service Worker 缓存数据,包括页面、图片等资源,实现离线访问,提高性能。
  • 使用 Webpack 编译器生成 hash 编码的文件名字,让缓存失效时间延长。

# 7. 常用的 http状态码有哪些

  1. 200 OK:请求成功,返回响应内容。
  2. 301 Moved Permanently:永久重定向到另一个 URI,客户端应使用新的 URI 访问资源。
  3. 302 Found:临时重定向到另一个 URI,客户端应使用原有 URI 访问资源。
  4. 304 Not Modified:未被修改,客户端可以使用缓存中的资源。
  5. 400 Bad Request:客户端的请求有语法错误或无法被服务器理解。
  6. 401 Unauthorized:表示请求需要用户身份认证。
  7. 403 Forbidden:表示服务器拒绝执行请求,通常是因为客户端没有权限访问资源。
  8. 404 Not Found:表示请求的资源不存在,或者服务器不想让客户端知道该资源的存在。
  9. 500 Internal Server Error:表示服务器在处理请求时发生了内部错误,通常是因为程序异常或配置错误。
  10. 503 Service Unavailable:表示服务器暂时无法处理请求,通常是因为过载或维护。

# 8. 虚拟 DOM 在哪个阶段产生的

虚拟 DOM 是 Vue 用来提高渲染性能的一种技术,它可以将真实的 DOM 结构抽象为一个普通的 JS 对象,然后通过比对新旧虚拟 DOM 树的差异,来更新必要的真实 DOM 节点,从而减少对真实 DOM 的操作。

  • onBeforeMount:在这个阶段,Vue 会将模板编译为 render 函数,然后调用 render 函数生成一个虚拟 DOM 树,这是第一次生成虚拟 DOM 树。

  • onBeforeUpdate:在这个阶段,Vue 会在响应式数据发生变化时,重新调用 render 函数生成一个新的虚拟 DOM 树,然后与旧的虚拟 DOM 树进行对比,找出差异,并更新到真实 DOM 上。

# 9. 什么是 diff 运算

Vue diff 运算是 Vue 用来实现虚拟 DOM 的更新的一种算法,它可以快速地比较新旧虚拟 DOM 树之间的差异,并将差异应用到真实 DOM 上,从而减少对真实 DOM 的操作。Vue diff 运算有以下几个特点:

  • 只比较同级节点:Vue diff 运算不会跨层级地比较节点,而是只比较同一层级的节点,如果节点类型不同,就直接替换整个子树,如果节点类型相同,就继续比较子节点。

  • 采用双端比较:Vue diff 运算会同时从新旧虚拟 DOM 树的两端开始比较,即头头比较和尾尾比较,如果头头相同,就移动指针继续比较;如果尾尾相同,就移动指针继续比较;如果头尾相同,就交换位置并移动指针继续比较;如果都不相同,就用新节点的 key 在旧节点中查找匹配的节点,如果找到就移动到正确的位置,如果没找到就插入新节点。

  • 使用 key 优化:Vue diff 运算会使用 key 属性来标识每个节点的唯一性,这样可以避免重复创建或销毁节点,提高性能。因此,在使用 v-for 循环渲染列表时,最好给每个节点添加一个唯一的 key 属性

# 10. 浏览器缓存原理 (opens new window)

浏览器缓存是指浏览器将之前请求的资源缓存在本地,下次请求相同资源时可以直接从本地获取,从而加快页面加载速度以及减少网络带宽的压力。

浏览器缓存的原理可以概括为以下几个步骤:

  1. 当浏览器发起一个请求时,先检查缓存中是否已经存在该资源的副本。

  2. 如果存在,检查缓存资源的过期时间。如果未过期,则直接从缓存中获取资源;否则向服务器重新请求资源。

  3. 如果缓存中不存在该资源或者已过期,则向服务器发送请求,服务器响应请求并返回资源数据。同时,服务器也会在响应头中返回资源的缓存策略和过期时间等信息。

  4. 浏览器收到响应后,根据响应头中的缓存策略和过期时间等信息,决定是否缓存该资源,并将缓存的资源副本保存到本地缓存中。

  5. 当页面再次请求该资源时,可以直接从本地缓存中获取资源,而无需再次向服务器发起请求。

需要注意的是,缓存策略和过期时间是控制浏览器如何缓存资源的关键因素。常用的缓存策略包括强缓存和协商缓存。强缓存是指直接从本地缓存中获取资源,不向服务器发送请求;协商缓存是指向服务器发送请求,根据服务器返回的缓存策略和过期时间等信息来判断是否从缓存中获取资源。

# 11. 模块化有了解吗,讲讲 import 和 require 的区别?

  1. import 是 ES6 的语法,require 是 CommonJS 规范的语法。
  2. 在使用 import 时,文件名必须带有扩展名(如.js、.jsx、.ts 等),而在使用 require 时可以省略扩展名。
  3. import 具有解构赋值的能力,可以指定要导入的变量和函数,而 require 则只能导入整个模块。
  4. 在使用 import 时,模块路径必须是相对路径或者绝对路径,而在使用 require 时还可以使用模块名(在 Package.json 中配置的名称)。
  5. import 和 export 只能在代码的顶层使用,而 require 可以在任何地方使用。

# 12. webpack 热更新机制原理

  1. 启动 Webpack Dev Server,开启热更新功能;
  2. 当模块发生变化时,使用 webpack 自带的 watch mode 重新编译打包,并将编译状态和更新的模块信息发送给 Webpack Dev Server;
  3. Webpack Dev Server 将编译状态和更新的模块信息通过 websocket 发送给客户端,客户端通过 websocket 监听更新信息;
  4. 客户端收到更新信息后,使用 HMR 技术来更新页面中的模块。HMR 的核心是通过注入代码来替换应用的模块,同时保持应用的运行状态。
  5. 需要注意的是,热更新机制并不是所有模块都适用,例如修改了某些模块中的配置,需要重启服务才能生效,而修改 html、css 等文件可以直接热更新。

# 13. webpack 的原理

  1. 入口点分析:Webpack 将入口模块文件作为起点,分析整个模块依赖关系,形成一个依赖关系树。根据依赖关系树,Webpack 将所有模块打包成一个或多个 bundle 文件。
  2. 模块解析:在分析依赖关系时,Webpack 会识别模块中的导入语句和导出语句,采用模块工厂将模块解析后打包为一个函数,该函数返回一个包含模块导出内容的对象。
  3. 打包并优化:Webpack 将所有模块打包成 bundle 时,会对模块进行优化,以提高打包效率和加载速度。优化包括代码压缩、文件合并、资源提取、动态链接库打包等。
  4. 输出:当 Webpack 完成所有模块打包并优化后,将生成一个或多个 bundle 文件,根据配置将打包后的文件输出到指定路径或者内存中。
  5. 与 loaders 和 plugins 的交互:Webpack 支持在打包过程中使用 loaders 和 plugins,loaders 用于对模块文件的转换和解析,plugins 则用于在打包过程的不同阶段执行一些额外的任务(如资源优化、bundle 拆分、动态注入等)。

# 14. vite 原理是什么?

  1. Vite 利用浏览器原生 ES 模块的特性,以 ES 模块为粒度将代码拆分为更小的模块。这样做的好处是每个模块可以在需要的时候动态加载,减少冗余的加载时间。
  2. Vite利用 HTTP/2 的多路复用特性,将对多个模块文件的请求合并为一个请求,减少了往返时间和网络负载。
  3. 在首次加载过程中,Vite 会将请求的内容经过 hash 处理存入内存缓存,再次请求时通过判断是否命中缓存,避免了网络请求。
  4. 利用 Rollup 和 Esbuild 进行即时编译
  5. Vite 能做到零等待时间的热重载,这是因为每次保存都会预构建,只有当浏览器去请求对应的请求时,才会将预构建替换为最新修改,并通知浏览器协议切换到新版本。

# 15. 11.微信小程序原理是什么?

微信 App 里包含 javascript 运行引擎。微信 App 里包含 WXML/WXSS 处理引擎,最终会把界面翻译成系统原生的界面,并展示出来。这样做的目的是为了提供和原生 App 性能相当的用户体验。

# 16. Git 如何覆盖某次 commit

git rebase -i commitID 命令 git commit --amend 命令进行覆盖操作 git rebase --continue 命令继续 rebase 操作 git push --force 命令强制覆盖远程分支上的 commit。

# 17. react 的 class 生命周期

  1. constructor(props)
  2. static getDerivedStateFromProps(props, state)
  3. render()
  4. componentDidMount() 挂载
  5. shouldComponentUpdate(nextProps, nextState) 更新
  6. componentDidUpdate 组件更新后被调用,可用于更新后的一些操作

# 18. hooks 为何有一些规则使用条件

可能会导致代码难以理解、维护困难或出现意想不到的错误

# 19. 什么是 CSS 的继承性,那些属性可继承,哪些不可以?

  1. 文字相关属性: font-family、font-size、font-weight、font-style 等。
  2. 颜色属性: color、background-color。
  3. 文本相关属性: line-height、text-align、text-transform 等。
  4. 链接相关属性: text-decoration、link、visited、hover、active 等。
  5. 列表属性: list-style-type、list-style-image 等。
  6. 表格属性: border-collapse、border-spacing 等。
  7. 元素显示属性: display、visibility。
  8. 百分比属性: 某些属性(如 padding、margin)中的百分比值可以继承。

# 20. 宏任务和微任务!!!

网址 (opens new window) 执行顺序:同步任务 ---> 微任务 ---> 宏任务 事件循环的基本流程: 执行一个宏任务 → 清空所有微任务 → (可选)页面渲染 → 执行下一个宏任务 → 重复。

  1. 从宏任务队列中,按照入队顺序,找到第一个执行的宏任务,放入调用栈,开始执行;
  2. 执行完该宏任务下所有同步任务后,即调用栈清空后,该宏任务被推出宏任务队列,然后微任务队列开始按照入队顺序,依次执行其中的微任务,直至微任务队列清空为止
  3. 当微任务队列清空后,一个事件循环结束
  4. 接着从宏任务队列中,找到下一个执行的宏任务,开始第二个事件循环,直至宏任务队列清空为止。

宏任务与微任务的执行时机:

  • 宏任务在事件循环的 “宏任务阶段” 执行,每次执行一个。

  • 微任务在 “当前宏任务执行完毕后、下一个宏任务开始前” 执行,会一次性清空所有微任务。

    16.1 宏任务是一组异步任务,这些任务通常由浏览器的事件触发器发起,并在主线程中按照顺序执行。常见的宏任务包括:

  1. setTimeout 和 setInterval;
  2. I/O 操作,例如读取文件、网络请求;
  3. DOM 事件,例如点击事件、输入事件;
  4. requestAnimationFrame、postMessage、MessageChannel、及 Node.js 环境中的 setImmediate;
  5. script 标签;

16.2 微任务是一个细微的异步任务,它的执行时机在宏任务之后、渲染之前。微任务通常在一个宏任务执行完毕后立即执行,而不需要等待其他宏任务。这使得微任务的执行优先级比宏任务高。常见的微任务包括:

  1. Promise 的 resolve 和 reject 回调;
  2. async/await 中的异步函数;
  3. MutationObserver;

时间循环举例说明

console.log('同步任务') // 主线程直接执行

setTimeout(() => {
  console.log('宏任务1') // 宏任务队列
}, 0)

Promise.resolve().then(() => {
  console.log('微任务1') // 微任务队列
})

// 执行顺序:
// 1. 同步任务(当前宏任务的一部分)
// 2. 微任务1(当前宏任务执行完毕后清空)
// 3. 宏任务1(下一次事件循环的宏任务阶段)
async function method() {
  new Promise(resolve => {
    console.log(4)
    resolve()
  }).then(async () => {
    const n = await method2()
    console.log(n)
    console.log(1)
  })
}

function method2() {
  const promise = new Promise(resolve => resolve(2))
  return promise
}

function main() {
  method()
  console.log(3)
}
main()
console.log(9)
// 4 3 9 2 1
// 如果1和n调换上下位置则是 4 3 9 1 2

//困难级别

async function method() {
  console.log(1)
  new Promise(resolve => resolve()).then(() => console.log(2))
  new Promise(resolve => {
    setTimeout(() => {
      resolve()
      new Promise(resolve => resolve()).then(() => console.log(3))
    }, 0)
  }).then(() => console.log(4))
  await method3()
  console.log(5)
  /* const n = await method2();
      console.log(n); */
}

function method3() {
  const promise = new Promise(resolve => {
    console.log(9)
    resolve()
  })
  return promise
}

function main() {
  method()
  new Promise(resolve => {
    console.log(10)
    resolve()
  }).then(() => {
    console.log(15)
  })
  console.log(11)
}

main()
console.log(12)
// 1, 9, 10, 11, 12,  2, 5, 15, 4, 3

# 21. 什么是异步

JavaScript 是一门单线程的编程语言.意味着在一个特定的时间点,只能有一个代码块在执行。当执行一个同步任务时,如果任务需要很长时间才能完成,如网络请求、文件读取等,整个程序会被阻塞,导致用户界面无响应,甚至造成卡顿的问题。这种情况在 Web 应用中尤其常见,因为 JavaScript 经常与网络请求、DOM 操作等耗时任务打交道。

# 22. Proxy 与 Object.defineProperty 优劣对比

Proxy 的优势如下:

  1. Proxy 可以直接监听对象而非属性;
  2. Proxy 可以直接监听数组的变化;
  3. Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  4. Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
  5. Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势如下:

兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。

# 23. 虚拟 DOM 的优缺点?

优点:

  1. 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;

  2. 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;

  3. 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点: 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

# 24. 虚拟 DOM 实现原理? 原文 (opens new window)

  1. 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  2. diff 算法 — 比较两棵虚拟 DOM 树的差异;
  3. pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

# 25. Vue 项目进行哪些优化?

  1. v-if 和 v-show 区分使用场景
  2. computed 和 watch 区分使用场景
  3. v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
  4. 长列表性能优化
  5. 事件的销毁
  6. 图片资源懒加载
  7. 路由懒加载
  8. 第三方插件的按需引入
  9. 优化长列表性能
  10. 服务端渲染 SSR or 预渲染
  11. 尽可能使用字体图标

# 26. SPA 单页面的理解,它的优缺点分别是什么?

优点:

  1. 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
  2. 基于上面一点,SPA 相对对服务器压力小;
  3. 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

  1. 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
  2. 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
  3. SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

# 27. computed 和 watch 的区别和运用的场景?

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的

# 28. 23-2 computed 和 methods 的区别是什么?

  1. 在使用时,computed 当做属性使用,而 methods 则当做方法调用
  2. computed 可以具有 getter 和 setter,因此可以赋值,而 methods 不行
  3. computed 无法接收多个参数,而 methods 可以
  4. computed 具有缓存,而 methods 没有 备注:获取当前时间放到 computed

# 29. 什么是作用域

作用域决定了变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

  1. 全局作用域
  2. 局部作用域 2.1 函数作用域 2.2 块级作用域

# 30. 作用域链概念

JavaScript 上每一个函数执行时,会先在自己创建的AO 上找对应属性值。若找不到则往父函数的 AO 上找,再找不到则再上一层的 AO,直到找到大 boss:window(全局作用域)。 而这一条形成的“AO 链” 就是 JavaScript 中的作用域链。

# 31. 原型链

每个对象(object)都有一个私有属性指向另一个名为原型(prototype)的对象。原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null。

隐式原型 (proto) 显示原型 (prototype)

# 32. BOM (Browser Object Model),浏览器对象模型,如:window、location、navigator、screen、history

# 33. 父元素设置 flex,子元素的 float: left;无效

<style>
.box {
    display: flex;
    justify-content: center;
}
.box-li {
    float: left;
}
</style>
<div class="box">
    <div class="box-li1">222</div>
    <div class="box-li">333</div>
</div>

# 34. 29.微前端框架优缺点

  1. 独立开发和部署
  2. 技术栈灵活性
  3. 独立部署和扩展
  4. 增量升级(只升级需要修改的部分,而不必重新构建整个应用。这可以减少升级过程中的风险和影响范围。)

缺点:

  1. 初始复杂性
  2. 运行时性能
  3. 兼容性和一致性:
  4. 学习曲线

# 35. TS 数据类型

1.boolean
2. number
3. string
4. object
number[] string[] 等 数据类型[]  或  Array<number> Array<string> 等 Array<数据类型>  //数组
5. any[] //不确定个数,不确定类型的数组,但是不好的地方是就没有实时的编译提示了
6. unknown //任何类型的安全形式
7. undefined null
//默认情况下null和undefined是所有类型的子类型,但是如果想把其赋值给某个类型的变量,想要不报错首先要关闭严格模式
8. void //没有类型,如 function f:void () { ... } 无返回值
9. never //函数无法返回的类型。
//例如一个会抛出异常的函数 就可以写 function f(msg:string):never {throw new Error(msg)}

# 36. Vue3.x 响应式数据原理是什么?

在 Vue 2 中,响应式原理就是使用的 Object.defineProperty 来实现的。但是在 Vue 3.0 中采用了 Proxy,抛弃了 Object.defineProperty 方法。 究其原因,主要是以下几点:

Object.defineProperty 无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应 Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy 可以劫持整个对象,并返回一个新的对象。 Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。 Proxy 有多达 13 种拦截方法 Proxy 作为新标准将受到浏览器厂商重点持续的性能优化

Proxy 只会代理对象的第一层,那么 Vue3 又是怎样处理这个问题的呢? 判断当前 Reflect.get 的返回值是否为 Object,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测。 监测数组的时候可能触发多次 get/set,那么如何防止触发多次呢? 我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger。

# 37. v-model 双向绑定的原理是什么?

  1. v-model 是 :value + @input 方法的语法糖
  2. text 和 textarea 元素使用 value 属性和 input 事件
  3. checkbox 和 radio 使用 checked 属性和 change 事件
  4. select 字段将 value 作为 prop 并将 change 作为事件

# 38. call 、bind、apply

  1. call 和 apply 是用于立即执行函数并传递参数的方法,其中 apply 接受参数的形式是数组。
  2. bind 是用于创建一个新函数,该函数会在稍后的调用中使用绑定的上下文。

# 39. WebSocket 心跳机制

  1. 保持 WebSocket 连接不被断开。
  2. 检测 WebSocket 连接状态,及时处理异常情况。
  3. 减少 WebSocket 连接及服务器资源的消耗。

# 40. WebSocket 的缺点和不足 WebSocket

在线地址 (opens new window)

  1. WebSocket 需要浏览器和服务器端都支持该协议
  2. WebSocket 会增加服务器的负担,不适合大规模连接的应用场景。

# 41. 说一下 vue 模版编译的原理是什么

简单说,Vue 的编译过程就是将 template 转化为 render 函数的过程。

  1. 生成 AST 树
  2. 优化
  3. codegen(转换成代码输出)

# 42. delete 和 Vue.delete 删除数组的区别是什么?

delete 只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。 Vue.delete 是直接将元素从数组中完全删除,改变了数组其他元素的键值。

# 43. 插槽与作用域插槽的区别是什么?

  1. 插槽的作用是子组件提供了可替换模板,父组件可以更换模板的内容
  2. 作用域插槽给了子组件将数据返给父组件的能力,子组件一样可以复用,同时父组件也可以重新组织内容和样式。

# 44. vue3.x 对比 vue2.x 变化

  1. 优化 Diff 算法,vue2 通过标记静态根节点优化 diff,Vue3 标记和提升所有静态根节点,diff 的时候只需要对比动态节点内容(https://juejin.cn/post/7275943802934149160#heading-16)
  2. 源码组织方式变化:使用 TS 重写
  3. 支持 Composition API:基于函数的 API,更加灵活组织组件逻辑(vue2 用的是 options api)
  4. 响应式系统提升:Vue3 中响应式数据原理改成 proxy,可监听动态新增删除属性,以及数组变化
  5. 打包体积优化:移除了一些不常用的 api(inline-template、filter)
  6. 生命周期的变化:使用 setup 代替了之前的 beforeCreate 和 created
  7. Vue3 的 template 模板支持多个根标签
  8. Vuex 状态管理:创建实例的方式改变,Vue2 为 new Store , Vue3 为 createStore
  9. Route 获取页面实例与路由信息:vue2 通过 this 获取 router 实例,vue3 通过使用 getCurrentInstance/ userRoute 和 userRouter 方法获取当前组件实例
  10. Props 的使用变化:vue2 通过 this 获取 props 里面的内容,vue3 直接通过 props
  11. 父子组件传值:vue3 在向父组件传回数据时,如使用的自定义名称,如 backData,则需要在 emits 中定义一下

# 45. vue 为什么采用异步渲染

  • 因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染;所以为了性能考虑,Vue 会在本轮数据更新后,再去异步更新视图。

  • 异步渲染的原理:

  1. 调用 notify( ) 方法,通知 watcher 进行更新操作
  2. 依次调用 watcher 的 update 方法
  3. 对 watcher 进行去重操作(通过 id)放到队列里
  4. 执行完后异步清空这个队列,nextTick(flushSchedulerQueue)进行批量更新操作

# 46. 组件中写 name 选项有哪些好处

  1. 可以通过名字找到对应的组件( 递归组件:组件自身调用自身 )
  2. 可以通过 name 属性实现缓存功能(keep-alive)
  3. 可以通过 name 来识别组件(跨级组件通信时非常重要)
  4. 使用 vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的使用 vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的

# 47. vue 项目中的性能优化

  1. 尽量减少 data 中的数据,data 中的数据都会增加 getter 和 setter,会收集对应的 watcher
  2. v-if 和 v-for 不能连用
  3. 如果需要使用 v-for 给每项元素绑定事件时使用事件代理
  4. SPA 页面采用 keep-alive 缓存组件
  5. 在更多的情况下,使用 v-if 替代 v-show
  6. key 保证唯一
  7. 使用路由懒加载、异步组件
  8. 防抖、节流
  9. 第三方模块按需导入
  10. 长列表滚动到可视区域动态加载
  11. 图片懒加载
  12. SEO 优化 12-1. 预渲染 12-2. 服务端渲染 SSR
  13. 压缩代码
  14. 使用字体图标
  15. 使用 cdn 加载第三方模块
  16. sourceMap 优化
  17. 骨架屏
  18. PWA
  19. gzip 压缩
  20. 浏览器缓存和服务器缓存

# 48. 你的接口请求一般放在哪个生命周期中?为什么要这样做?

接口请求可以放在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

  1. 推荐放在 created 1-1 能更快获取到服务端数据,减少页面 loading 时间 1-2 SSR 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于代码的一致性 1-3 created 是在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图。如果在 mounted 钩子函数中请求数据可能导致页面闪屏问题

# 49. 什么是 vue 生命周期

对于 vue 来讲,生命周期就是一个 vue 实例从创建到销毁的过程。

# 50. vue 生命周期的作用是什么

在生命周期的过程中会运行着一些叫做生命周期的函数,给予了开发者在不同的生命周期阶段添加业务代码的能力。

# 51. js 事件机制执行的顺序

1.捕获阶段, 2.目标阶段, 3.冒泡阶段,

# 52. 微前端原理

  1. 1.监听路由变化
    • hash 路由: window.onhashchange
    • history 路由: history.go back、forward
  2. 匹配子应用
  3. 加载子应用
  4. 渲染子应用 直接输出 js 是不行的,使用 eval 函数来处理
  5. 沙箱隔离
const shadow = subApp.attachShadow({ mode: 'open' })
shadow.innerHTML = `
   <p>这个是通过 shadow dom 添加的内容</p>
   <style>
    p {color: red}
   </style>
`

# 53. 如何优化 SQLite 数据库性能?

  1. 数据库设计优化 采用合理的数据库设计,包括表结构设计、索引设计和数据类型选择等,能够有效地提高数据库的查询效率。
  2. 事务处理优化 在进行大量数据插入、删除和更新操作时,使用事务处理机制,能够提高数据库的性能和数据的完整性。
  3. SQL 语句优化 优化 SQL 查询语句,包括选择合适的查询条件、减少子查询、使用合适的连接方式等,能够提高数据库查询效率。
  4. 索引优化 在查询经常使用的列上创建索引,能够提高查询效率,但同时也会降低数据插入和更新的速度。
  5. 内存优化 通过调整 SQLite 的内存参数,如缓存大小、页面大小等,能够提高 SQLite 的性能。
  6. 数据库缩减 定期清理数据库中无用的数据,包括过期数据和重复数据,能够提高数据库查询效率和存储空间利用率。

# 54. some 和 find

都是找到了第一个匹配的项就退出循环,不同的是 some 返回的是布尔值,而 find 返回的是找到的那一项。

# 55. for of 和 for in 都是用来遍历的属性

  1. for in 正常的获取了对象的 key 值(或下标), for of 不能遍历对象
  2. for of 不同与 forEach, 它可以与 break、continue 和 return 配合使用,也就是说 for of 循环可以随时退出循环。

# 56. reduce 的参数有哪些

let sum = arr.reduce((prev, cur, index, arr) => {
  // console.log('必须上一次调用回调返回的值,或者是提供的初始值(initialValue):', prev)
  // console.log('必需。当前元素,从第二个开始:', cur)
  // console.log('可选。当前元素的索引:', index)
  // console.log('可选。当前元素所属的数组对象:', arr)
  return prev
})

# 57. js 生成新数组的方法, let arr = [1, 2, 3, 4, 5];

  1. concat()
  2. slice()
  3. filter()
  4. map()
  5. reverse()
  6. Array.from(arr, (item) => item * 2)

# 58. js 改变原数组的方法

  1. push
  2. splice
  3. shift
  4. pop
  5. unshift
  6. forEach
  7. fill

# 59. 如何判断两个数组的内容是否相等

  1. toString() , 处理对象等复杂数据类型时可能会出现问题
  2. JSON.stringify() 可以处理一些复杂的数据类型,但也有一些限制,比如无法处理包含循环引用的对象
  3. 循环遍历,判断每一项是否相等
function arraysAreEqual(arr1, arr2) {
  if (arr1.length !== arr2.length) {
    return false
  }

  for (let i = 0; i < arr1.length; i++) {
    if (arr1[i] !== arr2[i]) {
      return false
    }
  }

  return true
}

const array1 = [1, 2, 3]
const array2 = [1, 2, 3]
const result = arraysAreEqual(array1, array2) // 返回 true

# 60. map 和 forEach 的区别

相同点 (1)都是循环遍历数组中的每一项。 (2)每次执行匿名函数都支持三个参数,参数分别为 item(当前每一项),index(索引值),arr(原数组)。 (3)匿名函数中的 this 都是指向 window。 (4)只能遍历数组。

不同点:

  1. map()会分配内存空间存储新数组并返回,forEach()不会返回数据
  2. forEach()允许 callback 更改原始数组的元素。map()返回新的数组
  3. map 里可以用 return 返回一个数组,而 foreach 里用 return 不起作用,foreach 不能用 break,会直接报错;

# 61. git pull 是两个指令的组合:git fetch 和 git merge。

# 62. git 中 rebase、reset、revert 有什么区别?

  1. Rebase(变基):重新组织提交历史,合并分支或消除差异。
  2. Reset(重置):移动 HEAD 和分支引用以回退或前进到特定的提交,修改提交历史。
  3. Revert(还原):创建一个新的提交,撤销指定提交的更改,保持提交历史的完整性。

# 63. type 和 interface 区别

interface 主要用于定义对象的形状,支持声明合并(可以声明多个)和 extends 继承,而 type 具有更强大的特性,可以用于更广泛的类型操作,type 可以使用联合类型(|)和交叉类型(&)进行类型组合、映射类型。

# 64. 什么是泛型, 泛型的具体使用?

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。即推断类型

# 65. 父子组件的生命周期执行顺序

父组件 beforeCreate> 父组件 created > 父组件 beforeMount > 子组件 beforeCreate> 子组件 created > 子组件 beforeMount > 子组件 mounted > 父组件 mounted

# 66. Vue3 的 v-if 优先级高于 v-for; Vue2 相反

# 67. 经典的闭包问题

for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log('p:', i)
  }, 1000)
}
console.log('p2:', i)
// 解析:
// 1:使用var声明i的情况,首先输出5,然后输出5次5
// 2:使用let声明,i is not defined"; 然后输出0-4

# 68. Vue 初始化做了哪些东西

new Vue({ options }) data、props、编译模版,直至挂载 mounted

# 69. 哪个生命周期能访问 data 的数据

答案:create

# 70. el 在哪个生命周期才能访问

答案:mounted

# 71. 2 个构造函数相等的方法

function fun1() {
  this.property1 = 'value1'
}

function fun2() {
  this.property2 = 'value2'
}

// 让 fun2 的原型指向 fun1 的原型
fun2.prototype = Object.create(fun1.prototype)

// 创建实例
let p1 = new fun1()
let p2 = new fun2()

// 检查相等性
console.log(p1 instanceof fun1) // true
console.log(p1 instanceof fun2) // false

console.log(p2 instanceof fun1) // true
console.log(p2 instanceof fun2) // true

# 72. 计算属性为何有缓存功能?

  1. computed 接受的一个 fn,返回值要用.value 来访问
  2. computed 缓存功能,当它的响应式依赖更新时才会重新执行 fn,否则不会被调用。

# 73. Vue 3 的 nextTick 利用 Promise 的 then 方法将回调放到微任务队列中,以确保回调函数

const callbacks = []

function flushCallbacks() {
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
let timer
function nextTick(callback) {
  callbacks.push(callback)
  if (!timer) {
    // 利用 Promise 的 then 方法将回调放到微任务队列中
    Promise.resolve().then(flushCallbacks)
    // 或者使用 MutationObserver
    // const observer = new MutationObserver(flushCallbacks);
    // observer.observe(document.createTextNode(''), { characterData: true });
    // observer.disconnect();
  }
}

// 使用 nextTick
nextTick(() => {
  console.log('Callback executed')
})

// 修改数据
data.value = 'new value'

# 74. AMD、CMD、ESM 、CommonJS

模块化要解决的问题:

  1. 如何包装一个模块的的代码,使之不污染模块外的代码;
  2. 如何唯一标识一个模块;
  3. 如何在不增加全局变量的情况下将模块的 API 暴露出去;

AMD 是 RequireJS 在的推广和普及过程中被创造出来 运行时加载 (异步加载,异步执行) CMD 是 SeaJS 在的推广和普及过程中被创造出来 运行时加载 (异步加载,同步执行) CommonJS 是 node 的模块化规范,也主要应用于服务器端 运行时加载 (同步加载,同步执行) ESM(ES6 module) 编译时加载或叫做(“静态加载”),即是在编译时(import()是运行时加载)处理模块依赖关系 (同步加载同步运行, 异步加载同步执行)

# 75. flex:1 是哪几个熟悉的简写

  1. flex-grow:1
  2. flex-shrink: 0
  3. flex-basis: auto

# 76. 面向对象的概念 (OOP)

面向对象优点:易维护、易扩展、易复用,有封装、继承、多态的特性、更容易设计出耦合的系统。缺点:性能比面向过程低。

# 77. 面向过程的概念

效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。缺点:需要深入的思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大

# 78. 函数式编程是一种历史悠久的编程范式

# 79. tcp 和 udp

  1. TCP 是面向连接的传输。UDP 是无连接的传输
  2. TCP 有流量控制、拥塞控制,检验数据数据按序到达,而 UDP 则相反。
  3. TCP 的路由选择只发生在建立连接的时候,而 UDP 的每个报文都要进行路由选择
  4. TCP 是可靠性传输,他的可靠性是由超时重发机制实现的,而 UDP 则是不可靠传输
  5. TCP 适合用于传输大量数据,UDP 适合用于传输小量数据
  6. UDP 因为少了很多控制信息,所以传输速度比 TCP 速度快

# 80. electron-updater 全量更新

# 81. 网络传输 7 层协议

  1. 物理层
  2. 数据层
  3. 网络层
  4. 传输层
  5. 会话层
  6. 表示层
  7. 应用层

# 82. http1.0 - 3.0

HTTP 1.0:

  1. 每个请求都需要建立新的 TCP 连接,造成了较大的开销。
  2. 不能复用连接,无法进行流水线处理,导致效率低下。

HTTP 1.1:

  1. 引入了持久连接(Keep-Alive),允许多个请求和响应复用同一个 TCP 连接,减少了连接建立和关闭的开销。
  2. 引入了流水线处理,可以同时发送多个请求,无需等待前一个请求的响应

HTTP 2.0:

  1. 使用二进制帧替代了 HTTP 1.x 的文本格式,减少了数据量和解析开销。
  2. 引入了多路复用技术,可以在一个 TCP 连接上同时发送多个请求和响应,提高了并发性能。
  3. 支持服务器主动推送(Server Push),可以在客户端请求之前将相关资源主动推送给客户端。

HTTP 3.0:

  1. 使用 UDP 协议替代了 TCP 协议,减少了延迟和拥塞控制的问题。
  2. 使用QUIC 协议,提供可靠性、安全性和流量控制等功能。
  3. 使用 TLS 1.3 作为底层安全协议。

# 83. 什么是 QUIC

QUIC 被设计用于替代传统的传输层协议(如 TCP)以提供更快的连接建立和安全数据传输。

  1. 多路复用
  2. 零 RTT 连接建立
  3. 前向错误纠正
  4. 自适应拥塞控制
  5. 快速握手
  6. 动态密钥更新
  7. 加密

# 84. 什么情况下使用装饰器

  1. 代码重
  2. 代码维护
  3. AOP(面向切面编程)
  4. 性能优化
  5. 框架和库
  6. 添加元信息 (依赖注入)

# 85. 什么是依赖注入

  1. 依赖注入(Dependency Injection,DI)是一种设计模式,它是指将依赖(依赖对象)的创建和使用解耦,使得对象之间松耦合,从而实现可测试性和可维护性。
  2. 依赖注入的实现方式有三种:构造函数注入、接口注入、属性注入。
  3. 构造函数注入:通过构造函数参数来传递依赖对象。
  4. 接口注入:通过接口来传递依赖对象。
  5. 属性注入:通过属性来传递依赖对象。

# 86. Vue 中的 h 函数和 render 函数的区别

  • 82.1 h 函数:用于构建虚拟 DOM 节点(单个节点)。
  • 82.2 render 函数:调用 h 函数,生成完整的虚拟 DOM 树,并返回给 Vue。 文档地址 (opens new window)

# 87. 理解 async/await

async/await 是 Generator 的语法糖,通过状态机和 Promise 实现‘异步转同步’:

  • 83.1 await 暂停函数,返回 Promise → ⏸️ 状态保存
  • 83.2 Promise 完成后恢复执行 → 🔄 事件循环调度
  • 83.3 代码顺序与逻辑顺序一致,但本质仍为异步 → 🚫 非阻塞”

# 88. 为什么 const 声明的对象可以修改属性?

因为 const 保证的是引用地址不变,而对象是引用类型,只要地址不变,里面的属性是可以修改的。

# 89. 打包时 hash 码是如何⽣成的

MD5 哈希算法生成 128 位二进制值,转换为 32 位十六进制字符串(如 a1b2c3d4...)。

  1. 原理层面:hash 码是通过 MD5 算法对文件内容或构建信息生成的唯一标识。

  2. 工程应用:根据资源类型选择不同 hash 策略(JS 用 chunkhash,CSS 用 contenthash)。

  3. 核心目标:通过文件名变化强制浏览器加载新资源,同时最大限度利用缓存。

    类型 生成依据 作用域 应用场景
    [hash] 整个构建过程 所有输出文件共享 适用于单页应用(SPA)
    [chunkhash] 每个入口 chunk 的内容 不同入口独立 适用于多页应用(MPA)或拆分模块
    [contenthash] 单个文件的内容 每个文件独立 适用于 CSS、图片等静态资源

# 89.1. 缓存失效问题:

若使用[hash],任何文件修改都会导致所有资源 URL 变化,建议优先使用[contenthash]。

# 89.2. 开发环境优化:

开发环境建议使用[hash]或不使用 hash,避免频繁重新构建。

# 89.3. HMR 冲突:

热更新(HMR)模式下 hash 值可能频繁变化,可关闭 HMR 或使用[hash]。

# 90. Buffer 是什么?

Buffer 是一个内置类,用于表示二进制数据(如文件内容、网络流、加密数据等)。它是 Node.js 处理 I/O 操作(如文件读写、网络通信)的基础,提供了高效的二进制数据处理能力。 核心特点

# 90.1. 直接操作内存

Buffer 直接分配原始内存(不经过 JavaScript 堆),适合处理大量二进制数据,避免垃圾回收的性能开销。没有引用指向 Buffer 时,内存会被垃圾回收。

# 90.2. 固定长度

创建后大小固定,无法动态调整(但可通过 Buffer.concat() 合并多个 Buffer)。

# 90.3. 高效性能

专为二进制数据设计,提供多种编码转换(如 UTF-8、Base64、Hex 等)和位操作方法。

# 90.4. 全局可用

无需 require() 即可在 Node.js 环境中使用。

# 91. 什么是事件委托?

它利用 事件冒泡(Event Bubbling) 原理,将事件监听器绑定到父元素而非具体的子元素,从而减少事件监听器的数量,提升性能并简化代码。

# 91.1. 87.1 事件委托与直接绑定对比

特性 直接绑定 事件委托
监听器数量 每个元素一个监听器 仅父元素一个监听器
动态元素 需手动为新元素绑定监听器 自动支持新元素
内存占用 高(尤其大量元素时)
代码复杂度 分散(每个元素单独处理) 集中(父元素统一处理)

# 91.2. 87.2 事件委托的优势:

  • 减少内存占用:只需一个监听器处理多个子元素的事件,而非为每个子元素单独绑定监听器。
  • 动态元素支持:新增的子元素自动获得事件处理能力,无需额外绑定监听器。
  • 代码简洁:集中管理事件逻辑,避免重复代码。

# 92. 浏览器是否支持 CommonJs 规范?

浏览器不原生支持 CommonJS 规范。 借助工具 Webpack、Browserify

# 93. npm 和 yarn 的问题

# 93.1. 幽灵依赖

举个例子:你的项目只安装了 A 包(npm install A)。但是 A 包自己依赖了 B 包。因为是扁平化结构,B 包也会被提升到 node_modules 的根目录。

# 93.2. 磁盘空间的巨大浪费

如果你电脑上有 10 个项目,这 10 个项目都依赖了 lodash,那么在 npm/yarn 的模式下,你的磁盘上就会实实在在地存着 10 份一模一样的 lodash 代码。

# 93.3. 安装速度的瓶颈

虽然 npm 和 yarn 都有缓存机制,但在安装依赖时,它们仍然需要做大量的 I/O 操作,去复制、移动那些文件。当项目越来越大,node_modules 动辄上 G 的时候,那个安装速度,真的让人等到心焦。

# 94. pnpm 解决了什么问题

pnpm 的全称是“performant npm”,意为“高性能的 npm”。它解决上面所有问题的核心武器,就两个字:链接。

强制只能使用 pnpm 安装依赖,其他方式不允许。

{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}

# 95. 发布策略对比

金丝雀发布(Canary Release),也常被称为灰度发布,是一种旨在平滑、可控地推出新版本软件的部署策略。其核心思想是:不立即将新版本完全替换旧版本,而是先让一小部分用户流量(例如 5%)使用新版本。 ​ 经过观察,确认新版本运行稳定、无重大问题后,再逐步增加导流比例(例如 20% -> 50% -> 100%),直至所有用户都切换到新版本 。

策略 核心思想 优势 挑战
金丝雀发布 逐步将用户流量从旧版本迁移到新版本。 风险可控,回滚速度快,资源占用少(无需两套环境) 发布周期长,流量调度和监控体系复杂
蓝绿部署 同时部署完整的新(绿)旧(蓝)两套版本,通过切换流量入口发布。 发布和回滚极快,版本状态明确 资源成本高(需两套环境),数据库兼容难
滚动发布 逐步停止旧版本实例并启动新版本实例(K8s Deployment 默认策略)。 节约资源,实现简单 版本混杂,不易回滚,缺乏稳定基准环境
上次更新: