Vue知识点
- 前端知识点
- 生命周期
- 双向绑定原理
- 组件间传值
- MVVM
- ref和$refs
- 对象动态添加属性无法响应
- style标签中的scope
- [provide 和 inject](https://cn.vuejs.org/v2/api/#provide-inject)
- Vue源码分析
- vue-router
- v-model原理
- 模板编译
- 虚拟DOM
- beforeDestory
- v-for 和 v-if为什么不能连用
- vue-router原理
- vue响应式原理
- Vuex原理
- [computed 和 watch区别](https://www.cnblogs.com/jiajialove/p/11327945.html)
- Vue3
- Vue中key的作用
- render函数
- @click事件对象e和自定义参数
前端知识点
生命周期
beforeCreate、created等类似于一个状态,而created(),mounted()这些钩子函数是将这个状态保持一段时间,并在开始下一个阶段之前进行的一些操作。例如beforeCreate()就是在完成初始化时间&生命周期后,初始化注入&校验之前的一个状态,可以规定一些操作。
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm) // 初始化渲染
callHook(vm, 'beforeCreate') // 调用生命周期钩子函数
initInjections(vm) //初始化injections
initState(vm) // 初始化props,methods,data,computed,watch
initProvide(vm) // 初始化 provide
callHook(vm, 'created') // 调用生命周期钩子函数
export function mountComponent (vm,el,hydrating) {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
callHook(vm, 'beforeMount')
let updateComponent
// 首先执行渲染函数vm._render()得到一份最新的VNode节点树,
// 然后执行vm._update()方法对最新的VNode节点树与上一次渲染的旧VNode节点树进行对比并更新DOM节点(即patch操作),完成一次渲染
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
双向绑定原理
https://github.com/DMQ/mvvm
- 首先要使数据对象变得可观测
let car = observable({
'brand':'BMW',
'price':3000
})
- 通过
defineReactive
使得对象的每个属性变得可观测时为每个属性维护一个收集器Dep
function defineReactive (obj,key,val) {
let dep = new Dep()
...
}
- 当有对象使用
v-bind
,v-model
等方法对数据进行绑定时,会产生一个观测值watcher
对象
<div v-if="car.brand" />
new watcher(vm, 'car.brand', cb) // vm 是观测者,这里类似这个div,cb是更新函数
watcher
的构造函数中会先取car.brand
的值从而调用car.brand
的get
方法,在getter
中调用dep.depend
将watcher
加入到dep.subs
中,实现绑定
class Watcher {
constructor(vm,exp,cb){
...
this.value = this.get(); // 将自己添加到订阅器的操作,同时读取car.brand
}
}
Object.defineProperty(obj, key, {
get(){
dep.depend();
return val;
},
}
- 当数据发生更新时会调用
car.brand
的set
中的dep.notify
方法,再遍历dep
所维护的subs
数组,调用watcher.update
更新
组件间传值
父组件向子组件传值
父组件中使用v-bind绑定属性,子组件通过prop接收属性.
父组件
<div v-bind:title='pspdragon'></div>
子组件
export default {
props: {
title: {
type: String,
default: 'hello world'
}
}
}
子组件向父组件传值
子组件通过$emit提交事件,并可以携带参数,父组件通过v-on监听子组件提交的事件
父组件
<div @childfn="handler"></div>
export default {
// ...
methods: {
handler(payload) { //payload是子组件传递事件的参数
console.log(payload)
}
}
}
子组件
<div @click="click"></div>
export default {
methods: {
click(){
this.$emit('childfn', para)
}
}
}
兄弟组件间传值
兄弟组件间可以通过eventbus传值
eventbus.js
创建一个事件总线并导出
import Vue from 'vue'
const EventBus = new Vue()
export EventBus
组件A
<button @click="sendMsg"></button>
<script>
import { EventBus } from "../eventbus.js";
export default {
methods: {
sendMsg() {
EventBus.$emit("aMsg", '来自A页面的消息');
}
}
};
</script>
组件B
<p>{{msg}}</p>
<script>
import { EventBus } from "../eventbus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
EventBus.$on("aMsg", (msg) => {
// A发送来的消息
this.msg = msg;
});
}
};
</script>
MVVM
MVVM 是 Model-View-ViewModel 的缩写。mvvm 是一种设计思想。Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑;View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来,ViewModel 是一个同步 View 和 Model 的对象。
在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
ref和$refs
对象动态添加属性无法响应
有时在 data
中声明了一个对象属性 obj
,需要在之后动态的为 obj
添加属性,但是之后直接通过obj.prop = 3
这样的方式添加的属性无法对其进行响应,所以需要通过this.$set
的方式添加属性
style标签中的scope
https://www.cnblogs.com/goloving/p/9119460.html
vue 在单文件组件中设置样式时提供了一个 scoped
属性
<style scoped>
@media (min-width: 250px) {
.list-container:hover {
background: orange;
}
}
</style>
scoped
attribute 会自动添加一个唯一的 attribute (比如 data-v-21e5b78) 为组件内 CSS 指定作用域,编译的时候 .list-container:hover 会被编译成类似 .list-container[data-v-21e5b78]:hover。
css深度选择器
在使用了 scoped
时候如果需要覆盖 element-ui
或 iView
等的样式时会出现无效的情况,这时候通过css深度选择器
解决子组件使用scoped
而父组件无法覆盖其样式.
.parent >>> .children{ /* ... */ }
.parent /deep/ .children{ /* ... */ }
// vue 3.0 以后需要使用 ::v-deep 的写法
.parent ::v-deep .children{ /* ... */ }
provide 和 inject
Vue源码分析
vue-router
导航守卫
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
在导航完成前获取数据
当路由跳转到新页面需要请求的数据返回较慢时,为了避免路由已经跳转,但是没有数据出现页面空白的情况所以使用导航守卫 beforeRouteEnter
。
这样在数据加载完成前就会停留在当前页面,同时为了解决在数据加载完成前没有反应的问题,使用加载进度条提高用户体验,这里使用了NProgress
v-model原理
模板编译
- 解析模板生成
抽象语法树AST
- 对
AST树
进行优化 - 根据
AST树
生成一个render函数
供组件挂载时调用 - 调用
render函数
就可以得到模板对应的VNode
进而生成虚拟DOM
虚拟DOM
通过VNode
类来实例化出不同类型的虚拟DOM
节点
DOM-Diff
: 对比新旧两份VNode
并找出差异的过程就是所谓的DOM-Diff
过程patch
: 把 DOM-Diff过程叫做patch过程- 创建节点:新的VNode中有而旧的oldVNode中没有,就在旧的oldVNode中创建。
- 删除节点:新的VNode中没有而旧的oldVNode中有,就从旧的oldVNode中删除。
- 更新节点:新的VNode和旧的oldVNode中都有,就以新的VNode为准,更新旧的oldVNode。
beforeDestory
- 组件销毁前解绑
$on
方法 - 清除自己定义的定时器
- 解除事件的绑定
scroll mousemove ...
v-for 和 v-if为什么不能连用
v-for
的优先级要高于v-if
会导致先构造循环再判断性能低下
vue-router原理
Vue.use(Router)
调用install
函数
通过mixin
全局注入router
属性,
this.__router.init
初始化路由系统 ,通过transitionTo跳转到某个路由- new VueRouter()
vue-router的构造函数中通过createMatcher 建立一个matcher实例负责匹配路径, 扁平化用户传入的数据,创建路由映射表
根据mode创建不同的history实例 - router-view 函数式组件
- 当改变浏览器
url
时,会对url
进行监听,获取url
中的新路由并调用transition.to
进行跳转 - 当通过
router.push
进行跳转时,会调用transition.to
,并对浏览器的记录栈history
进行修改
h5 history
vue响应式原理
数组
- 用对象的原型create一个拦截器
arrayMethod
,并将拦截器的和数组相关的方法全部重写,在其中进行发送变化通知等操作 - 在将对象变得可观测时,将数据的
__proto__
设置为拦截器arrayMethod
* 支持__proto__
直接赋值
* 不支持__proto__
,循环把arrayMethod
的所有属性赋值给对象
Vue2缺陷
- 默认会递归
- 数组改变length是无效的
- 对象不存在的属性不能被拦截
Vue3
通过proxy
递归创建响应式对象
通过WeakMap
创建对象和代理结果的双向映射表,为了避免重复代理
effect(fn)
创建响应式effect,先执行fn,再将effect存到栈中
effect(fn)
: 副作用,即数据发生变化时会执行传入的fn
track(target, key)
: 收集依赖,如果target中的key发生变化,执行effectStack中的effect
创建一个大的WeakMap
:key是target,value是一个 map,map是对象的属性和effect的对应关系
trigger(target, key)
: 取得targetMap
中的effect
targetMap: weakMap {
对象target: map {
对象属性:set [effect1, effect2 ...]
}
}
effect调用时会先执行一遍 -> 代理的get -> track收集依赖 -> 创建targetMap
修改对象属性 -> 代理的set -> trigger -> 取得targetMap中所有 的effect -> 执行effect
Vuex原理
install 中通过mixin全局注入store
state
- store类的构造函数中通过
new Vue()
创建 vue 实例 vm
data 中定义state用户传入的options.state
从而将数据变为响应式
this.vm = new Vue({
data: {
state: options.state
}
})
定义一个实例方法,直接返回 vue 实例的 state
get state() {
return this.vm.state
}
getters
- 在constructor中新建一个getters属性
- 通过循环遍历用户传入的options.getters,将options.getters的函数名作为属性名传入getters
并通过Object.defineProperty定义get执行 原本的getters函数
// 用户传入的 getters
options.getters: {
add: (state) => {
return state.a + 1
}
}
// Vuex的构造函数中
this.getters = {}
Object.defineProperty(this.getters, 'add', {
get: () => {
return options.getters['add'](state)
}
})
mutations
- store类中的commit函数,参数mutationName, payload
- 发布订阅模式,构造函数中定义this.mutations为用户传入的mutations,循环 mutations 进行函数劫持
commit函数中执行对应this.mutations[mutationName](payload)
函数
// 用户传入的 mutations
options.mutations: {
increment (state, n) {
state.count += n
}
}
// Vuex的构造函数中
this.mutations = {}
forEach(option.mutations, (mutationName, val) => {
this.mutations[mutationName] = (payload) => {
// 执行用户传入的函数
options.mutations.increment(this.state, payload)
}
})
// commit 函数
commit = (mutationName, payload) => {
this.mutations[mutationName](payload)
}
actions
- 发布订阅者模式,原理与
mutations
相似 - 做一个监控,判断异步方法是不是都在
actions
中执行的
// 用户传入的 actions
options.actions: {
increment ({ commit }) {
commit('increment')
}
}
// Vuex的构造函数中
this.actions = {}
forEach(actions, (actionName, val) => {
this.actions[actionName] = (payload) => {
// 这里传入的第一个参数是 this 即 store 实例,可以结构出 { commit, state ... }
options.actions[actionName](this, payload)
}
})
// dispatch 函数
dispatch = (actionName, payload) => {
this.actions[actionName](payload)
}
funmodule
预期格式
root = {
_raw: rootModule, // 用户传入的 options
state: rootModule.state, // options. state
children: {
a: {
_raw: aModule,
_children: {},
state: aModule.state
}
}
}
- 在moduleCollection中递归的对options进行格式化
- 递归的安装模块 installModule(this, this.state, [], this.module.root)
将 state, getters, mutations, actions 安装到当前 store 实例上
// class ModuleCollection
// installModule
function installModule (store, rootState, path, rawModule) {
let getters = rawModule._raw.getters
if (path.length > 0) { // 当前的path长度大于零说明有子模块
let parentState = path.slice(0, -1).reduce((root, current) => {
return root[current]
}, rootState)
Vue.set(parentState, path[path.length - 1], rawModule.state)
}
if (getters) {
// 定义this.getters
forEach (getters, (getterName, value) => {
Object.defineProperty(store.getters, getterName, {
get: () => {
return value(rawModule.state)
}
})
})
}
let mutations = rawModule._raw.mutations
if (mutations) {
forEach(mutations, (mutationName, value) => { // [fn, fn]
let arr = store.mutations[mutationName] || (store.mutations[mutationName] = [])
arr.push((payload) => {
value(rawModule.state, payload)
})
})
}
let actions = rawModule._raw.actions
if (actions) {
forEach(actions, (actionName, value) => {
let arr = store.actions[actionnName] || (store.actions[actionName] = [])
arr.push((store, payload)
})
})
}
forEach(rawModule._children, (moduleName, rawModule) => {
installModule(store, rootState, path.cocat(moduleName), rawModule)
})
}
Vuex 模块化+命名空间后, 如何调用其他模块的 state, actions, mutations, getters ?
computed 和 watch区别
Vue3
ref 和 reactive
ref
用来处理基本数据类型,reactive
用来处理对象(递归深度响应式)- 如果用
ref
处理对象,内部会将对象转换为reactive
的代理对象 ref
内部:通过value
属性添加getter/setter
来实现对数据的劫持ref
数据操作:在js中通过.value,在模板中不需要(内部解析模板时会自动添加.value)