该部分描述项目的背景,目的,目标,范围以及预期结果。它可以帮助参与者了解项目的重要性和在完成项目后将实现什么。
该部分包括项目的时间表以及每个阶段的关键日期。它应该显示出明确的里程碑,以便主要决策者,项目经理和其他利益相关者进行跟踪和监视。
该部分详细描述产品的功能和需求,包括产品的整体目标,重要特性和用户故事等。
该部分定义产品的体系结构,包括数据结构,程序流程图和技术堆栈等。
该部分概述产品的质量保证和测试计划,包括如何测试,测试环境,如何记录和跟踪缺陷等。
该部分描述产品部署的计划,包括硬件和软件要求,并提供操作手册和维护指南。
该部分识别和管理项目相关的所有风险和机会。这有助于建立明确的业务对策和计划,以避免或缩减风险和利用机会。
该部分定义参与项目的人员组成及各个角色的职责范围,以确保在项目管理和执行过程中能够实现良好的协作和沟通。
http状态码有哪些虚拟 DOM 是 Vue 用来提高渲染性能的一种技术,它可以将真实的 DOM 结构抽象为一个普通的 JS 对象,然后通过比对新旧虚拟 DOM 树的差异,来更新必要的真实 DOM 节点,从而减少对真实 DOM 的操作。
onBeforeMount:在这个阶段,Vue 会将模板编译为 render 函数,然后调用 render 函数生成一个虚拟 DOM 树,这是第一次生成虚拟 DOM 树。
onBeforeUpdate:在这个阶段,Vue 会在响应式数据发生变化时,重新调用 render 函数生成一个新的虚拟 DOM 树,然后与旧的虚拟 DOM 树进行对比,找出差异,并更新到真实 DOM 上。
Vue diff 运算是 Vue 用来实现虚拟 DOM 的更新的一种算法,它可以快速地比较新旧虚拟 DOM 树之间的差异,并将差异应用到真实 DOM 上,从而减少对真实 DOM 的操作。Vue diff 运算有以下几个特点:
只比较同级节点:Vue diff 运算不会跨层级地比较节点,而是只比较同一层级的节点,如果节点类型不同,就直接替换整个子树,如果节点类型相同,就继续比较子节点。
采用双端比较:Vue diff 运算会同时从新旧虚拟 DOM 树的两端开始比较,即头头比较和尾尾比较,如果头头相同,就移动指针继续比较;如果尾尾相同,就移动指针继续比较;如果头尾相同,就交换位置并移动指针继续比较;如果都不相同,就用新节点的 key 在旧节点中查找匹配的节点,如果找到就移动到正确的位置,如果没找到就插入新节点。
使用 key 优化:Vue diff 运算会使用 key 属性来标识每个节点的唯一性,这样可以避免重复创建或销毁节点,提高性能。因此,在使用 v-for 循环渲染列表时,最好给每个节点添加一个唯一的 key 属性
浏览器缓存是指浏览器将之前请求的资源缓存在本地,下次请求相同资源时可以直接从本地获取,从而加快页面加载速度以及减少网络带宽的压力。
浏览器缓存的原理可以概括为以下几个步骤:
当浏览器发起一个请求时,先检查缓存中是否已经存在该资源的副本。
如果存在,检查缓存资源的过期时间。如果未过期,则直接从缓存中获取资源;否则向服务器重新请求资源。
如果缓存中不存在该资源或者已过期,则向服务器发送请求,服务器响应请求并返回资源数据。同时,服务器也会在响应头中返回资源的缓存策略和过期时间等信息。
浏览器收到响应后,根据响应头中的缓存策略和过期时间等信息,决定是否缓存该资源,并将缓存的资源副本保存到本地缓存中。
当页面再次请求该资源时,可以直接从本地缓存中获取资源,而无需再次向服务器发起请求。
需要注意的是,缓存策略和过期时间是控制浏览器如何缓存资源的关键因素。常用的缓存策略包括强缓存和协商缓存。强缓存是指直接从本地缓存中获取资源,不向服务器发送请求;协商缓存是指向服务器发送请求,根据服务器返回的缓存策略和过期时间等信息来判断是否从缓存中获取资源。
微信 App 里包含 javascript 运行引擎。微信 App 里包含 WXML/WXSS 处理引擎,最终会把界面翻译成系统原生的界面,并展示出来。这样做的目的是为了提供和原生 App 性能相当的用户体验。
git rebase -i commitID 命令 git commit --amend 命令进行覆盖操作 git rebase --continue 命令继续 rebase 操作 git push --force 命令强制覆盖远程分支上的 commit。
可能会导致代码难以理解、维护困难或出现意想不到的错误
网址 (opens new window) 执行顺序:同步任务 ---> 微任务 ---> 宏任务 事件循环的基本流程: 执行一个宏任务 → 清空所有微任务 → (可选)页面渲染 → 执行下一个宏任务 → 重复。
微任务队列清空为止;事件循环结束;宏任务与微任务的执行时机:
宏任务在事件循环的 “宏任务阶段” 执行,每次执行一个。
微任务在 “当前宏任务执行完毕后、下一个宏任务开始前” 执行,会一次性清空所有微任务。
16.1 宏任务是一组异步任务,这些任务通常由浏览器的事件触发器发起,并在主线程中按照顺序执行。常见的宏任务包括:
16.2 微任务是一个细微的异步任务,它的执行时机在宏任务之后、渲染之前。微任务通常在一个宏任务执行完毕后立即执行,而不需要等待其他宏任务。这使得微任务的执行优先级比宏任务高。常见的微任务包括:
时间循环举例说明
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
JavaScript 是一门单线程的编程语言.意味着在一个特定的时间点,只能有一个代码块在执行。当执行一个同步任务时,如果任务需要很长时间才能完成,如网络请求、文件读取等,整个程序会被阻塞,导致用户界面无响应,甚至造成卡顿的问题。这种情况在 Web 应用中尤其常见,因为 JavaScript 经常与网络请求、DOM 操作等耗时任务打交道。
Proxy 的优势如下:
Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
优点:
保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
缺点: 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
优点:
缺点:
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的
作用域决定了变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。
JavaScript 上每一个函数执行时,会先在自己创建的AO 上找对应属性值。若找不到则往父函数的 AO 上找,再找不到则再上一层的 AO,直到找到大 boss:window(全局作用域)。 而这一条形成的“AO 链” 就是 JavaScript 中的作用域链。
每个对象(object)都有一个私有属性指向另一个名为原型(prototype)的对象。原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null。
隐式原型 (proto) 显示原型 (prototype)
<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>
缺点:
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)}
在 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。
简单说,Vue 的编译过程就是将 template 转化为 render 函数的过程。
delete 只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。 Vue.delete 是直接将元素从数组中完全删除,改变了数组其他元素的键值。
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染;所以为了性能考虑,Vue 会在本轮数据更新后,再去异步更新视图。
异步渲染的原理:
接口请求可以放在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
对于 vue 来讲,生命周期就是一个 vue 实例从创建到销毁的过程。
在生命周期的过程中会运行着一些叫做生命周期的函数,给予了开发者在不同的生命周期阶段添加业务代码的能力。
1.捕获阶段, 2.目标阶段, 3.冒泡阶段,
const shadow = subApp.attachShadow({ mode: 'open' })
shadow.innerHTML = `
<p>这个是通过 shadow dom 添加的内容</p>
<style>
p {color: red}
</style>
`
都是找到了第一个匹配的项就退出循环,不同的是 some 返回的是布尔值,而 find 返回的是找到的那一项。
let sum = arr.reduce((prev, cur, index, arr) => {
// console.log('必须上一次调用回调返回的值,或者是提供的初始值(initialValue):', prev)
// console.log('必需。当前元素,从第二个开始:', cur)
// console.log('可选。当前元素的索引:', index)
// console.log('可选。当前元素所属的数组对象:', arr)
return prev
})
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
相同点 (1)都是循环遍历数组中的每一项。 (2)每次执行匿名函数都支持三个参数,参数分别为 item(当前每一项),index(索引值),arr(原数组)。 (3)匿名函数中的 this 都是指向 window。 (4)只能遍历数组。
不同点:
interface 主要用于定义对象的形状,支持声明合并(可以声明多个)和 extends 继承,而 type 具有更强大的特性,可以用于更广泛的类型操作,type 可以使用联合类型(|)和交叉类型(&)进行类型组合、映射类型。
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。即推断类型
父组件 beforeCreate> 父组件 created > 父组件 beforeMount > 子组件 beforeCreate> 子组件 created > 子组件 beforeMount > 子组件 mounted > 父组件 mounted
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
new Vue({ options }) data、props、编译模版,直至挂载 mounted
答案:create
答案:mounted
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
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'
模块化要解决的问题:
AMD 是 RequireJS 在的推广和普及过程中被创造出来 运行时加载 (异步加载,异步执行) CMD 是 SeaJS 在的推广和普及过程中被创造出来 运行时加载 (异步加载,同步执行) CommonJS 是 node 的模块化规范,也主要应用于服务器端 运行时加载 (同步加载,同步执行) ESM(ES6 module) 编译时加载或叫做(“静态加载”),即是在编译时(import()是运行时加载)处理模块依赖关系 (同步加载同步运行, 异步加载同步执行)
面向对象优点:易维护、易扩展、易复用,有封装、继承、多态的特性、更容易设计出耦合的系统。缺点:性能比面向过程低。
效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。缺点:需要深入的思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大
HTTP 1.0:
HTTP 1.1:
HTTP 2.0:
HTTP 3.0:
QUIC 被设计用于替代传统的传输层协议(如 TCP)以提供更快的连接建立和安全数据传输。
async/await 是 Generator 的语法糖,通过状态机和 Promise 实现‘异步转同步’:
因为 const 保证的是引用地址不变,而对象是引用类型,只要地址不变,里面的属性是可以修改的。
MD5 哈希算法生成 128 位二进制值,转换为 32 位十六进制字符串(如 a1b2c3d4...)。
原理层面:hash 码是通过 MD5 算法对文件内容或构建信息生成的唯一标识。
工程应用:根据资源类型选择不同 hash 策略(JS 用 chunkhash,CSS 用 contenthash)。
核心目标:通过文件名变化强制浏览器加载新资源,同时最大限度利用缓存。
| 类型 | 生成依据 | 作用域 | 应用场景 |
|---|---|---|---|
[hash] | 整个构建过程 | 所有输出文件共享 | 适用于单页应用(SPA) |
[chunkhash] | 每个入口 chunk 的内容 | 不同入口独立 | 适用于多页应用(MPA)或拆分模块 |
[contenthash] | 单个文件的内容 | 每个文件独立 | 适用于 CSS、图片等静态资源 |
若使用[hash],任何文件修改都会导致所有资源 URL 变化,建议优先使用[contenthash]。
开发环境建议使用[hash]或不使用 hash,避免频繁重新构建。
热更新(HMR)模式下 hash 值可能频繁变化,可关闭 HMR 或使用[hash]。
Buffer 是一个内置类,用于表示二进制数据(如文件内容、网络流、加密数据等)。它是 Node.js 处理 I/O 操作(如文件读写、网络通信)的基础,提供了高效的二进制数据处理能力。 核心特点
Buffer 直接分配原始内存(不经过 JavaScript 堆),适合处理大量二进制数据,避免垃圾回收的性能开销。没有引用指向 Buffer 时,内存会被垃圾回收。
创建后大小固定,无法动态调整(但可通过 Buffer.concat() 合并多个 Buffer)。
专为二进制数据设计,提供多种编码转换(如 UTF-8、Base64、Hex 等)和位操作方法。
无需 require() 即可在 Node.js 环境中使用。
它利用 事件冒泡(Event Bubbling) 原理,将事件监听器绑定到父元素而非具体的子元素,从而减少事件监听器的数量,提升性能并简化代码。
| 特性 | 直接绑定 | 事件委托 |
|---|---|---|
| 监听器数量 | 每个元素一个监听器 | 仅父元素一个监听器 |
| 动态元素 | 需手动为新元素绑定监听器 | 自动支持新元素 |
| 内存占用 | 高(尤其大量元素时) | 低 |
| 代码复杂度 | 分散(每个元素单独处理) | 集中(父元素统一处理) |
浏览器不原生支持 CommonJS 规范。 借助工具 Webpack、Browserify
举个例子:你的项目只安装了 A 包(npm install A)。但是 A 包自己依赖了 B 包。因为是扁平化结构,B 包也会被提升到 node_modules 的根目录。
如果你电脑上有 10 个项目,这 10 个项目都依赖了 lodash,那么在 npm/yarn 的模式下,你的磁盘上就会实实在在地存着 10 份一模一样的 lodash 代码。
虽然 npm 和 yarn 都有缓存机制,但在安装依赖时,它们仍然需要做大量的 I/O 操作,去复制、移动那些文件。当项目越来越大,node_modules 动辄上 G 的时候,那个安装速度,真的让人等到心焦。
pnpm 的全称是“performant npm”,意为“高性能的 npm”。它解决上面所有问题的核心武器,就两个字:链接。
强制只能使用 pnpm 安装依赖,其他方式不允许。
{
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}
金丝雀发布(Canary Release),也常被称为灰度发布,是一种旨在平滑、可控地推出新版本软件的部署策略。其核心思想是:不立即将新版本完全替换旧版本,而是先让一小部分用户流量(例如 5%)使用新版本。 经过观察,确认新版本运行稳定、无重大问题后,再逐步增加导流比例(例如 20% -> 50% -> 100%),直至所有用户都切换到新版本 。
| 策略 | 核心思想 | 优势 | 挑战 |
|---|---|---|---|
| 金丝雀发布 | 逐步将用户流量从旧版本迁移到新版本。 | 风险可控,回滚速度快,资源占用少(无需两套环境) | 发布周期长,流量调度和监控体系复杂 |
| 蓝绿部署 | 同时部署完整的新(绿)旧(蓝)两套版本,通过切换流量入口发布。 | 发布和回滚极快,版本状态明确 | 资源成本高(需两套环境),数据库兼容难 |
| 滚动发布 | 逐步停止旧版本实例并启动新版本实例(K8s Deployment 默认策略)。 | 节约资源,实现简单 | 版本混杂,不易回滚,缺乏稳定基准环境 |