Skip to content

深入浅出Vue.js

变化检测

对于对象:在getter中收集依赖,在setter中触发依赖,无法侦测属性的增删

对于数组:在getter中收集依赖,在拦截器中触发依赖,无法侦测数组索引改值、修改数组长度

vm.$set

对于数组,内部使用了splice方法,会自动向依赖发送通知,对于对象的新增属性则用defineReactive将新增属性转化为getter/setter形式,之后手动向依赖发送通知 vm.$delete:和vm.$set差不多

vm.$watch:对Watcher的一种封装,选项参数deepimmediate

发布订阅

Dep:发布者

Watcher:订阅者

只要触发getter就会触发收集依赖的逻辑,并添加到实例化的Dep中去,当数据变化时Dep会触发notify方法通知Watcher执行update方法,而update方法会执行参数中的回调函数

Object.defineProperty(对象,属性名,描述符对象)ECMAScript中有两种属性,数据属性和访问器属性,数据属性包括:configurable、enumberable、writable、value,其中只有configurabletrue才能将属性修改为访问器属性,为false时不能通过delete删除,访问器属性包括configurable、enumberable、get、set,所以源码中在使用Object.defineProperty(对象,属性名,描述符对象)前会通过Object.getOwnPropertyDescriptor(对象,属性名)方法获取属性描述符中的configurable来判断是否为访问器属性,通过Object.defineProperty、Object.defineProperties、Object.create方法添加的属性,其configurable、enumberable、writable默认为false,通过var定义的全局变量其configurablefalse

虚拟DOM

概念:通过状态生成虚拟节点树,然后使用虚拟节点树进行渲染,更新视图,在渲染前会将新生成的虚拟节点树和上一次生成的虚拟节点树进行比对,只渲染不同的部分

本质:用Javascript运算成本替换DOM操作的执行成本

核心:patch,将vnode渲染成真实的DOM

VNode

VNode是一个类,可以生成不同类型的vnode实例,而不同类型的vnode表示不同类型的真实DOM元素

简单地说,vnode可理解为节点描述对象,描述了应该怎样去创建真实的DOM节点

类型:注释节点、文本节点、元素节点、组件节点、函数式节点、克隆节点

patch

虚拟DOM的核心,将vnode渲染成真实的DOM

过程:创建节点、删除节点、修改节点

目的:修改DOM节点,渲染视图

基本流程:

st=>start: patch
op=>operation: 是否存在oldVnode
in=>inputoutput: 使用vnode创建真实节点并插入到视图中节点的旁边
cond1=>condition: 是否存在oldVnode
cond2=>condition: oldVnode和vnode是否是同一个节点
e1=>end: 使用vnode创建节点并插入视图 
e2=>end: 使用patchVnode进行更详细的比对与更新操作
e3=>end: 将视图中的旧节点删除
st->cond1
cond1(no)->e1
cond1(yes)->cond2
cond2(no)->in->e3
cond2(yes,right)->e2

模板编译

将模板编译成渲染函数,而渲染函数的作用就是每次执行它,就会使用最新的状态生成一份新的vnode,然后使用这个vnode进行渲染

大体上分为三部分:将模板解析为AST(抽象语法树)(解析器)、遍历AST标记静态节点(优化器)、使用AST生成渲染函数(代码生成器)

vm.$nextTick

将回调延迟到下次DOM更新周期之后执行

使用场景:当更新了状态后需要对新DOM做一些操作,此时还获取不到更新后的DOM,因为还没有渲染,这个时候就可以使用nextTick方法

当状态发生改变时,watcher会得到通知,然后触发虚拟DOM的渲染流程。但watcher触发渲染这个操作不是同步的,而是异步的,vue中有一个队列,每当需要渲染时,会将watcher推送到这个队列中,在下一次的事件循环中再让watcher触发渲染的流程

vue的异步DOM更新机制

vue中变化侦测的通知只发送到组件级别,组件内用到的所有状态都会通知到同一个watcher,然后虚拟DOM会对整个组件进行diff比对并更改DOM

在同一事件循环中,当状态发生变化时,会将通知的watcher实例添加到队列中,并在添加前会先检查队列中是否已存在相同的watcher,只有不存在时才添加到队列中,然后在下一次事件循环中才让队列中的watcher触发渲染流程并清空队列

这样可以保证即便在同一事件循环中有多个状态发生改变,watcher最终也执行一次渲染流程,提高性能

最佳实践

v-for循环设置key,虚拟DOM算法中对比新旧节点时辨识虚拟节点

路由切换组件不变(页面切换到同一路由但不同参数时,组件的生命周期不会重复触发),解决方法:

  • 路由导航守卫 beforeRouteUpdate(推荐)

  • 观察$route对象的变化

  • router-view组件添加key(暴力)

避免v-forv-if同时使用

为组件样式设置作用域scoped

避免在scoped中使用元素选择器

避免隐形的父子组件通信

良好的代码顺序

  1. 组件/实例的选项顺序
  • 副作用(触发组件外的影响)
  • el
  • 全局感知(要求组件以外的知识)
  • name
  • parent
  • 组件类型(更改组件的类型)
  • functional
  • 模板修改器(改变模板的编译方式)
  • delimiters
  • comments
  • 模板依赖(模板内使用的资源)
  • components
  • directives
  • filters
  • 组合(向选项里合并属性)
  • extends
  • mixins
  • 接口(组件的接口)
  • inheritAttrs
  • model
  • props/propsData
  • 本地状态(本地的响应式属性)
  • data
  • computed
  • 事件(通过响应式事件触发的回调)
  • watch
  • 生命周期钩子(按照生命周期调用顺序)
    • beforeCreate
    • created
    • beforeMount
    • mounted
    • beforeUpdate
    • updated
    • activated
    • deactivated
    • beforeDestroy
    • destroyed
  • 非响应式的属性(不依赖响应式系统的实例属性)
  • methods
  • 渲染(组件输出的声明式描述)
  • template/render
  • renderError
  1. 元素特性的顺序
  • 定义(提供属性的选项)
  • is
  • 列表渲染(创建多个变化的相同元素)
  • v-for
  • 条件渲染(元素是否渲染/显示)
  • v-if
  • v-else-if
  • v-else
  • v-show
  • v-cloak
  • 渲染方式(改变元素的渲染方式)
  • v-pre
  • v-once
  • 全局感知(需要超越组件的知识)
  • id
  • 唯一的特性(需要唯一值的特性)
  • ref
  • key
  • slot
  • 双向绑定
  • v-model
  • 其他特性(所有普通的绑定或未绑定的特性)
  • 事件(组件事件监听器)
  • v-on
  • 内容(覆写元素的内容)
  • v-html
  • v-text