Vue知识点

前端知识点

生命周期

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
双向绑定

  1. 首先要使数据对象变得可观测
let car = observable({
	'brand':'BMW',
	'price':3000
})
  1. 通过defineReactive使得对象的每个属性变得可观测时为每个属性维护一个收集器Dep
function defineReactive (obj,key,val) {
   	let dep = new Dep()
   	...
}
  1. 当有对象使用v-bind, v-model等方法对数据进行绑定时,会产生一个观测值watcher对象
<div v-if="car.brand" />
new watcher(vm, 'car.brand', cb) // vm 是观测者,这里类似这个div,cb是更新函数
  1. watcher的构造函数中会先取car.brand的值从而调用car.brandget方法,在getter中调用dep.dependwatcher加入到dep.subs中,实现绑定
class Watcher {
		constructor(vm,exp,cb){
		    ...
		    this.value = this.get();  // 将自己添加到订阅器的操作,同时读取car.brand
		}
}
Object.defineProperty(obj, key, {
	get(){
		dep.depend();
		return val;
	},
}
  1. 当数据发生更新时会调用car.brandset中的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-uiiView 等的样式时会出现无效的情况,这时候通过css深度选择器解决子组件使用scoped而父组件无法覆盖其样式.

.parent >>> .children{ /* ... */ }

.parent /deep/ .children{ /* ... */ }
// vue 3.0 以后需要使用 ::v-deep 的写法
.parent ::v-deep .children{ /* ... */ }

provide 和 inject

Vue源码分析

vue-router

导航守卫

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 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 函数式组件
  1. 当改变浏览器url时,会对url进行监听,获取url中的新路由并调用transition.to进行跳转
  2. 当通过router.push进行跳转时,会调用transition.to,并对浏览器的记录栈history进行修改

h5 history

vue响应式原理

数组

  1. 用对象的原型create一个拦截器arrayMethod,并将拦截器的和数组相关的方法全部重写,在其中进行发送变化通知等操作
  2. 在将对象变得可观测时,将数据的__proto__设置为拦截器arrayMethod
    * 支持__proto__直接赋值
    * 不支持__proto__,循环把arrayMethod的所有属性赋值给对象

Vue2缺陷

  1. 默认会递归
  2. 数组改变length是无效的
  3. 对象不存在的属性不能被拦截

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

  1. ref 用来处理基本数据类型,reactive用来处理对象(递归深度响应式)
  2. 如果用ref处理对象,内部会将对象转换为reactive的代理对象
  3. ref内部:通过 value属性添加getter/setter来实现对数据的劫持
  4. ref数据操作:在js中通过.value,在模板中不需要(内部解析模板时会自动添加.value)

Vue中key的作用

render函数

@click事件对象e和自定义参数

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值