# 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() 的问题主要有三个:

  • 不能监听数组的变化
  • 必须遍历对象的每个属性
  • 必须深层遍历嵌套的对象

ProxyES2015 规范中被正式加入,它有以下几个特点:

  • 针对对象:针对整个对象,而不是对象的某个属性,所以也就不需要对 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 全局有三个守卫:

  1. router.beforeEach 全局前置守卫 进入路由之前
  2. router.beforeResolve 全局解析守卫(2.5.0+) 在 beforeRouteEnter 调用之后调用
  3. 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) => {
        // 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
        // ...
      },
    },
  ],
})

路由组件内的守卫

  1. beforeRouteEnter 进入路由前, 在路由独享守卫后调用 不能 获取组件实例 this,组件实例还没被创建
  2. beforeRouteUpdate 路由复用同一个组件时, 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 this
  3. 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 一致