2 / 8 原则
注意点
watch
watch的是引用类型,是拿不到 oldVal
v-for
key是什么作用?
利于 Diff 算法,性能优化
事件
事件被绑定到哪里?(此处可以与react的事件对比)
- event 对象是原生的
- 事件被挂载到当前对象
```vue
// prower by vite
{{ num }}
<a name="PxYhr"></a>
#### 生命周期
- 挂载阶段
- 更新阶段
- 销毁阶段
重点:父子组件的生命周期:<br />**父 beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted**
- **parent created -> child created -> child mounted -> parent mounted**
- **parent beforeUpdate -> child beforeUpdate -> child updated -> parent updated **
- **parent beforeDestory -> child beforeDestory -> child destroyed -> parent destoryed**
**重点:**
- **beforeCreate 阶段,vue 实例还未初始化,data observer 和 event/watcher 未调用**,所以对 data/methods/文档节点的调用无法得到正确数据
- **created 阶段,渲染 HTML 文档,dom 节点和 css 规则树和 js 文件被解析后,没有进入到浏览器的 render 过程(也就是说实例已被初始化,还未挂载到 $el 上,无法获取到对应的节点,但是可以获取到 data/methods 中的数据)**
- **beforeMount 与 created 类似**
- **mounted 阶段,浏览器完成了 dom 树和 css 规则树的渲染,完成对 render tree 的布局,浏览器调用渲染器 paint 在屏幕上显示(在 vue 中,vue 的 template 成功挂载到 $el 中,可以调用节点)**
<a name="QqjBS"></a>
#### vue-router
动态路由:`/:id`<br />懒加载:`() =>import([文件名])`
路由模式:hash,history
hash:
- hash变化会触发网页跳转,浏览器前进与后退
- hash变化不会刷新页面
- hash不会提交到server端
```javascript
window.onhashchange = function(event) {
console.log(location.hash)
}
history(需要server配合)
- url规范的路由,跳转不刷新页面
- history.pushState/history.replaceState
- window.onpopstate
// 监听浏览器的前进/后退 const state = {name: 'page1'} history.pushState(state, '', 'page1') window.onpopstate = funtion(event) { console.log(event.state, location.pathname) }
问题
Vue2
Vue对象的变化
Vue无法检测property的添加或删除。
因为Vue在初始化实例时会对property执行getter/setter转化,所以property必须在data对象上存在才能让Vue将它转为响应式的。
可以使用Vue.set(object, propertyName, value)向嵌套对象添加响应式。
如果你需要为已有对象赋值多个新的property,可以使用Object.assign。但是这样添加的新的property不会触发更新。这时需要创建原对象混合的新对象。
// 代替 Object.assign(this.someObject, { a: 1, b: 2 })
this.someObject = Object.assign({}, this.someObject, {a: 1, b: 2})
Vue数组的变化
Vue无法检测2种数组的变化:
- 利用索引直接设置一个数组项,
vm.items[indexOfItem] = newValue
- 修改数组的长度,vm.items.length = newLength
第一种有2种方法解决:
Vue.set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
第二种:
vm.items.splice(newLength)
Vue异步更新队列
Vue在更新DOM时时异步执行的。只要数据变化,Vue将开启一个队列,并缓存在同一事件循环所发生的数据变更。
如果同一watcher被多次触发,只会被推入到队列中一次。
重要:在缓存中去除重复数据可以避免造成不必要的计算和DOM操作。
在下一个事件循环’tick’,Vue刷新队列并执行。
Vue内部对异步队列尝试使用原生的Promise.then,MutationObserver,setImmediate,如果执行环境不支持,则会采用setTimeout(fn,0)代替。
Vue 有了数据劫持为什么还要 DOM diff?
现在前端框架有2种数据变动侦测方式,一种是 pull,一种是 push。
React是 pull,在进行 setState 操作后显示更新数据,使用 diff 算法一层层找出差异,然后 patch 到 DOM 树上,React 一开始不知道哪里变化了,只是知道变化了,然后暴力进行查找那个变化。
Vue 的响应式系统是 push 的代表,Vue 初始化的时候就对 data 的数据进行依赖收集,因此 Vue 能实时知道哪里发生了变化,一般绑定的细粒度过高,会生成大量的 Watcher 实例,则会造成过大的内存和依赖追踪的开销,而细粒度过低无法侦测到变化,因此,Vue 采用的是中等细粒度的方案,只针对组件级别进行响应式监听也就是 Push,这样就可以知道哪个组件发生了变化,再对组件进行 diff 算法找到具体变化的位置,这是 pull 操作,Vue 是 pull + push 结合进行变化侦测的。
https://zhuanlan.zhihu.com/p/101330697
https://juejin.cn/post/6844904031983239181#heading-19
为什么要引入虚拟 DOM,而不是直接把组件转换为真实的 DOM 元素?
更高效:当组件发生变化,利用虚拟 DOM 树可以对新旧 DOM 进行比较,找出区别,只更新发生变化的 DOM,让渲染更加高效。
更灵活:将原来直接渲染变成先构建虚拟 DOM,在进行渲染。相当于抽离出中间层,对应的渲染层可以针对浏览器的渲染,也可以针对小程序等其他平台的渲染。
响应式原理
Object.defineProperty问题:
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增属性和删除属性(Vue.set,Vue.delete)
当创建 Vue 实例的时候,vue 会遍历 data 选项的属性,并利用 Object.defineProperty 方法为每个属性添加 getter 和 setter 方法进行劫持(getter用来收集依赖,setter用来派发更新),并在内部追踪依赖,在属性被访问和修改时通知变化
每个组件实例都有相对应的 watcher 实例,会在组件渲染过程中记录依赖的所有属性(依赖收集),当改动后,会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。
总结:
vue 采用数据劫持结合发布-订阅模式,通过 Object.defineProperty 方法来劫持各个属性的 getter / setter,在数据变动时发布消息给订阅者,触发响应的监听回调
computed 实现原理
本质是一个惰性求值的观察者
内部实现了一个惰性的watcher,也就是 computed watch,它不会立刻求值,持有一个 dep 实例
通过this.dirty属性标记计算属性是否需要重新求值
当 computed 的依赖状态发生变化。就会通知整个惰性的 watcher,通过 this.dep.subs.length 判断有无订阅者,
- 有会重新计算,然后对比新旧值,如果变化会重新渲染。
- 没有把 this.dirty = true
computed 和 watch 有什么区别及应用场景?
区别:
computed 计算属性
- 依赖其他属性值
- computed属性值有缓存
- 只有它依赖的属性值发生了变化,下一次获取 computed 的值时才会重新计算computed值
watch:
- “观察”的作用,没有缓存性,类似于某些数据的监听回调,每当监听的数据发生变化都会执行相应回调进行后续操作
应用场景:
- computed:进行数值计算时,依赖其他数据
- watch 需要进行异步操作
为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
proxy 是一个包含另一个对象或函数,并允许你对其进行拦截的对象
Object.defineProperty 只能劫持对象的属性,所以需要遍历对象所有的属性,Vue2 里通过递归+遍历 data 对象来实现对数据的监控,如果属性的值也是个对象,需要深度遍历,显然劫持一个完整对象才是更好的选择。
Proxy 可以劫持一个完整的对象并返回一个新的对象。Proxy 不仅可以代理对象,也可以代理数组,还可以代理动态增加的属性。
为什么在 Vue2 中不能检测数组的变化?
Object.defineProperty本身有一定的监控数组下标变化的能力,但是从性能/体验的性价比考虑,尤大就弃用了这个特性
因为处于性能和用户体验性价比,性能代价和获得用户体验不成正比
Vue2中只对pop,push,shift,unshift,splice,sort,reverse
做了hack处理,其他数组的属性检测不到
Vue 中的 key 到底有什么作用?
key 给每个 vnode 的唯一 id,用于 diff 操作更加准确和快速
diff 算法:先进行新旧节点的首位交叉对比,当无法匹配时会用新节点的 key,与旧节点进行比对,从而找出相应的旧节点。(通过 tag 和 key 判断,是否是 sameNode)
更准确:带上 key 后不是简单的就地复用,而是通过 sameNode 函数判断 a.key=b.key,可以避免就地复用的情况。如果不加 key,会导致之前节点的状态被保留下来,产生一系列的 bug
更快速:key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度为 O(1)
nextTick 的原理
JS时单线程的,是基于事件循环的
事件循环的步骤:
- 所有同步任务在主线程上执行,形成一个执行栈
主线程之外,还存在一个任务队列,只要异步任务有了结果,就在任务队列上添加一个事件
一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件,那些对应的异步任务,结束等待状态,进入执行栈,开始执行
- 主线程不断重复第三步操作
主线程执行过程就是一个tick,所有异步结果都是通过“任务队列”来调度。
消息队列中存放着一个个的任务(task),任务分为2类:宏任务(macro task)和微任务(micro task),每个宏任务结束后,都要清空微任务。
常见的宏任务:setTimeout,MessageChannel,postMessage,setImmidate
常见的微任务:MutationObserver,Promise.then
再Vue2.5,宏任务的降级方法依次为:setImmidate,MessageChannel,setTimeout
vue 的 nextTick 方法的实现原理:
- vue使用异步队列的方式来控制DOM更新和nextTick回调先后执行
- micro task因为其高优先级特性,能确保队列中的微任务在事件循环前被执行完毕
- 考虑兼容问题,vue做了micro task 向 macro task的降级方案。
vue 是如何对数组方法进行变异的?
vue 的 data 为什么必须是函数?
.vue 文件实际上是一个类,在使用的过程中,其实是实例化的过程
因为组件时可以复用的,js 对象时引用关系,如果 data 是对象,那么子组件的 data 属性值就会相互污染,产生副作用。
而 new Vue 的实例不会被复用
Vue的事件机制,$on,$emit,$off,$once
vue事件机制本身就是一个发布-订阅模式的实现。
Vue的渲染过程?
Keep-alive的实现原理和缓存策略
vm.$set 实现原理
响应式的好处?
数据双向绑定,无需操作 DOM,数据修改自动更新视图
解释一下 jsx
是一个 js 的语法扩展。
在 js 里面写 DOM 元素。
vue的 template 和 jsx 的区别
模板优势
- 模板是一种更静态更具有约束的表现形式,任何可以解析HTML的引擎都能使用,迁移成本较低。
- 静态模板在编译时进行了比较多的优化。
jsx 的优势:
- 更加灵活,任何js代码都可以在jsx中执行
- 但是导致它在编译阶段优化比较困难,需要开发者自己优化
Vue可以提供2种渲染模式,默认为template,也可以通过render function实现更灵活的应用
虚拟 DOM 的好处
vue 虚拟 DOM 的优缺点
优点:
- 保证性能的下限,框架的虚拟DOM需要适配上层API可能产生的操作,它的一些DOM操作是普适的,所以它的性能不是最优的
- 无需手动操作DOM(双向数据绑定)
- 跨平台
缺点:
- 无法进行极致的性能优化
Vue-router 的 hash 模式和 history 模式的区别
- 显示上,
hash
模式带#
号,history
没有 - 底层实现上,
hash
模式利用了onhashchange
事件(监听location.hash
的改变),而history
模式利用HTML5 history
新增的2个方法(pushState
,replaceState
),pushState()
可以改变url地址且不会发送请求,replaceState()
可以读取历史记录栈,并且修改浏览器记录 - 当需要通过
URL
向后端发送HTTP
请求时(如用户手动输入URL
后回车或者刷新和重启浏览器),这时候history
模式需要后端支持。因为history
模式,前端的URL
需要与后端发送请求的URL
一致,如果后端没有处理的话,就会返回一个404
错误window.onhashchange = function() { // 需要截取一下 console.log(location.hash.slice(1)) }
使用history模式,后端不在返回404错误页面,需要前端覆盖所有路由情况,然后给出一个404页面。 服务器处理:所有路径都返回index.html文件
{path: '*', component: NoFoundComponent}
Vue2中Object.defineProperty
此方法不是一个可以shim的属性,所以 Vue 不支持 ie8 及更低版本
Vue3 中的 Proxy
这是 es6 的新特性,但是 Vue3 版本也使用了 Object.defineProperty 来兼容ie版本
将对象作为数据传递给组件实例时,Vue 会将其转换为 proxy,使 Vue 在 property 被访问和修改时执行依赖跟踪和更改通知。每个 property 将视为一个依赖项。
首次渲染组件会跟踪一组依赖项列表。反过来组件就成了每个 property 的订阅者,当 proxy 拦截到 set 操作时,该 property 将通知其所有的订阅的组件重新渲染
vuex 和 pinia 区别
https://juejin.cn/post/6986540472986501150
vuex优点:
- 支持调试功能,如时间旅行,和编辑
- 适用于大型/高复杂项目
缺点: - vue3开始,getter的结果不会像计算属性那样缓存
- vuex4有类型安全相关问题
pinia优点:
- 完整的ts支持
- 轻巧,1k
- store的action作为常规函数调用,不需要dispatch或者mapActions辅助函数
- 支持多个store
- 支持 Vue devtools、SSR 和 webpack 代码拆分
缺点: - 不支持时间旅行和编辑等调试功能