# Vue
# 1 nextTick
在下次 dom
更新循环结束之后执行延迟回调,可用于获取更新后的 dom
状态
- 新版本中默认是
mincrotasks
,v-on
中会使用macrotasks
macrotasks
任务的实现:setImmediate
/MessageChannel
/setTimeout
# 2 生命周期
init
initLifecycle/Event
,往vm上挂载各种属性callHook: beforeCreated
: 实例刚创建initInjection/initState
: 初始化注入和data
响应性created
: 创建完成,属性已经绑定, 但还未生成真实dom
- 进行元素的挂载:
$el / vm.$mount()
- 是否有
template
: 解析成render function
*.vue
文件:vue-loader
会将<template>
编译成render function
beforeMount
: 模板编译/挂载之前- 执行
render function
,生成真实的dom
,并替换到dom tree
中 mounted
: 组件已挂载
update
- 执行diff算法,比对改变是否需要触发UI更新
flushScheduleQueue
watcher.before
: 触发beforeUpdate
钩子 -watcher.run()
: 执行watcher
中的notify
,通知所有依赖项更新UI- 触发
updated
钩子: 组件已更新 actived / deactivated(keep-alive)
: 不销毁,缓存,组件激活与失活destroy
beforeDestroy
: 销毁开始- 销毁自身且递归销毁子组件以及事件监听
remove()
: 删除节点watcher.teardown()
: 清空依赖vm.$off()
: 解绑监听
destroyed
: 完成后触发钩子
上面是vue的声明周期的简单梳理,接下来我们直接以代码的形式来完成vue的初始化
new Vue({})
// 初始化Vue实例
function _init() {
// 挂载属性
initLifeCycle(vm)
// 初始化事件系统,钩子函数等
initEvent(vm)
// 编译slot、vnode
initRender(vm)
// 触发钩子
callHook(vm, 'beforeCreate')
// 添加inject功能
initInjection(vm)
// 完成数据响应性 props/data/watch/computed/methods
initState(vm)
// 添加 provide 功能
initProvide(vm)
// 触发钩子
callHook(vm, 'created')
// 挂载节点
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
// 挂载节点实现
function mountComponent(vm) {
// 获取 render function
if (!this.options.render) {
// template to render
// Vue.compile = compileToFunctions
let { render } = compileToFunctions()
this.options.render = render
}
// 触发钩子
callHook('beforeMounte')
// 初始化观察者
// render 渲染 vdom,
vdom = vm.render()
// update: 根据 diff 出的 patchs 挂载成真实的 dom
vm._update(vdom)
// 触发钩子
callHook(vm, 'mounted')
}
// 更新节点实现
funtion queueWatcher(watcher) {
nextTick(flushScheduleQueue)
}
// 清空队列
function flushScheduleQueue() {
// 遍历队列中所有修改
for(){
// beforeUpdate
watcher.before()
// 依赖局部更新节点
watcher.update()
callHook('updated')
}
}
// 销毁实例实现
Vue.prototype.$destory = function() {
// 触发钩子
callHook(vm, 'beforeDestory')
// 自身及子节点
remove()
// 删除依赖
watcher.teardown()
// 删除监听
vm.$off()
// 触发钩子
callHook(vm, 'destoryed')
}
# 3 Proxy 相比于 defineProperty 的优势
Object.defineProperty() 的问题主要有三个:
- 不能监听数组的变化
- 必须遍历对象的每个属性
- 必须深层遍历嵌套的对象
Proxy
在 ES2015
规范中被正式加入,它有以下几个特点:
- 针对对象:针对整个对象,而不是对象的某个属性,所以也就不需要对 keys 进行遍历。这解决了上述
Object.defineProperty()
第二个问题 - 支持数组:Proxy 不需要对数组的方法进行重载,省去了众多
hack
,减少代码量等于减少了维护成本,而且标准的就是最好的。
除了上述两点之外,Proxy
还拥有以下优势:
- Proxy 的第二个参数可以有 13 种拦截方法,这比起
Object.defineProperty()
要更加丰富 - Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下
Object.defineProperty()
是一个已有的老方法。
let data = { a: 1 }
let reactiveData = new Proxy(data, {
get: function(target, name){
// ...
},
// ...
})
# 4 vuex
state
: 状态中心mutations
: 更改状态actions
: 异步更改状态getters
: 获取状态modules
: 将state
分成多个modules
,便于管理
# 5 vue-router 有哪几种导航守卫
- 全局守卫
- 路由独享守卫
- 路由组件内的守卫
全局守卫
vue-router 全局有三个守卫:
- router.beforeEach 全局前置守卫 进入路由之前
- router.beforeResolve 全局解析守卫(2.5.0+) 在 beforeRouteEnter 调用之后调用
- router.afterEach 全局后置钩子 进入路由之后
// main.js 入口文件
import router from './router' // 引入路由
router.beforeEach((to, from, next) => {
next()
})
router.beforeResolve((to, from, next) => {
next()
})
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子')
})
路由独享守卫
也可以单独为某些路由单独配置守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
// ...
},
},
],
})
路由组件内的守卫
beforeRouteEnter
进入路由前, 在路由独享守卫后调用 不能 获取组件实例this
,组件实例还没被创建beforeRouteUpdate
路由复用同一个组件时, 在当前路由改变,但是该组件被复用时调用 可以访问组件实例this
beforeRouteLeave
离开当前路由时, 导航离开该组件的对应路由时调用,可以访问组件实例this
# 6 Vue 的路由实现: hash / history 模式
hash 模式:
浏览器 URL 中会显示 #
,#
以及 #
后面的字符称之为 hash
,可以用 window.location.hash
读取
监听 hash
模式用的是 hashchange
特点
- hash 虽然在 URL 中,但不被包括在 HTTP 请求中
- hash 不会重加载页面
history 模式:
history 采用HTML5的新特性,且提供了两个新方法: pushState()
, replaceState()
,可以对浏览器历史记录栈进行修改,只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求
监听 history
模式用的是 popstate
前端的 URL 必须和实际向后端发起请求的 URL 一致