画布 1
vue源码解析
  vue初始化
    定义属性原型方法
      initMixin
      stateMixin
      eventsMixin
      lifecycleMixin
      renderMixin
    定义静态属性方法
      initGlobalAPI
    构造器的默认选项
      component
      directive
      filter
      _base
    选项校验
      components规范校验
      props规范校验
      inject的校验
      directive校验规范
    合并策略
      常规选项的合并
        el的合并
        data的合并
        自带资源选项的合并
      生命周期钩子函数的合并
      watch选项的合并
      prop methods inject computed合并
  基础的数据代理检测
    Object.defineProperty
      基本概念
      优缺点
    Proxy
      基本概念
      优缺点
      vue中的应用
  实例挂载流程与模版编译
    俩个构建版本
      runtime only(只包含运行时的版本)
      runtime+compiler(同时包含编译器与运行时的版本)
    挂载流程
    编译流程
  完整渲染流程
    Virtual DOM是什么
    Virtual DOM的创建
    Virtual DOM映射成真实DOM
  基础组件剖析
    组件组册方式
    组件Vnode创建
    组件Vnode渲染真实DOM
    建立组件联系
    小结
  深入响应式系统构建
    数据初始化
      initProps
      initMethods
      initData
      initComputed
    响应式系统构建的核心概念
      Observer、Watcher、Dep
      data分析
      computed分析
      数组的重写
      对象异常检测
      nextTick实现
      用户手动添加watch
  diff算法
    定义
    实现
    思考
  vue的事件机制
    模版编译(解析成AST树)
    代码生成(AST树构建render函数)
    事件绑定
    自定义事件
  都可以算是对生命周期渲染方法的定义
  我们将选项对象传递给构造器进行初始化，通过initGlobalAPI方法初始化，Vue内部会有些默认选项保留在静态options上，通过内置的extend方法实现对象的合并
定义Vue原型上的_init方法，_init方法是new Vue执行的初始化代码
定义了跟数据相关的属性方法，例如this.$data、this.$props、this.$set、this.$delete等方法
会对原型上的事件相关做定义，文档中的vm.$on、vm.$off、vm.$once、vm.$emit就是在这定义的
定义和生命周期相关的方法
例如实现将虚拟dom渲染成真实dom的_update函数,用于强制刷新的方法$forceUpdate、用于销毁实例的$destory等。
定义原型上和渲染相关的方法。
例如将回调延迟到下次DOM更新循环之后执行的$nextTick方法，
用于生成虚拟dom的_render方法
定义了Vue的全局api方法，该方法所做功能汇总如下
为源码里的config配置做一层代理，可以通过Vue.config拿到默认的配置。
定义内部使用的工具方法，比如警告提示，对象合并。
定义set,delete,nextTick方法，其实原型上也有这些方法的定义
对Vue.components,Vue.directive,Vue.fiter的定义，这些都是默认的资源选项。
定义了Vue.use()、Vue.mixin()、Vue.extend()方法
Vue默认提供了keepAlive、transition、transitionGroup的内置组件
提供了v-model、v-show的内置指令
没有默认值
利用正则判断检测是否为非法的标签，例如非字母开头
new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$"))
不能使用Vue自身自定义的组件名，如slot、component，不能使用html的保留标签
虽然props有俩中书写形式，数组与对象，但最终都会转换为对象的形式。
inject选项有数组与对象俩中写法，校验规则与props的校验规则一致
vue允许我们自定义指令，并且提供了5个钩子函数 bind、inserted、update、componentUpdated、unbind。还提供了一种函数的简写：
{
directives: {
'color-swatch': function(el, binding) {
el.style.backgroundColor = binding.value
}
}
}
函数的写法会在bind、uodate钩子中触发相同的行为，并且不关心其他钩子。因此在对directives进行规范化时，针对函数的写法会将行为赋予bind，update钩子。
合并选项类型繁多，合并规则随着选项的不同也呈现差异。主要为以下俩点：
Vue针对每个规定的选项都有定义好的合并策略，如果合并的子父配置都具有相同的选项，则只需要按照规定好的策略进行选项合并。
Vue传递的选项是开放式的，所以也存在传递的选项没有自定义选项的情况，这时候由于不存在默认的合并策略，所以处理的原则是有子类配置选项则默认使用子类的，没有则选择父类的。
el提供一个页面上已存在的DOM元素作为Vue挂载目标，在子类或者子组件总无法定义el选项，因此el的合并策略是在保证选项只存在于根的Vue实例的情形下使用默认策略进行合并。
data策略最终调用mergeDataOrFn方法，用于区别当前vm是否是实例，或者是单纯的子父类的关系。如果是子父类的关系，需要对data选项进行规范校验，保证它的类型是一个函数而不是对象。
vue组件的data为什么一定要是一个函数，而不能是一个对象呢？
组件设计的目的是为了复用，每次通过函数创建相当于在一个独立的内存空间生成了一个data的副本，这样每个组件之间的数据不会互相影响。
自带资源指的是 components、directive、filter。合并逻辑很简单，首先会创建一个原型指向父类资源选项的空对象，再将子类选项复制给空对象。
目前vue的生命周期多达12个，子父组件的生命周期钩子函数是遵循什么样的规则合并的？
mergeHook是用于处理生命周期钩子合并的策略，合并原则如下：
如果子类和父类都拥有相同钩子选项，则将子类选项和父类选项合并。
如果父类不存在钩子选项，子类存在时，则以数组形式返回子类钩子选项。
当子类不存在钩子选项时，则以父类选项返回。
子父合并时，是将子类选项放在数组的末尾，这样执行钩子时，永远是父类选项优于子类选项的执行。
对于watch选项的合并，最终和父类选项合并成数组，并且数组的选项成员，可以是回调函数，选项对象或者函数名。
这几项的合并策略一致，简单概括就是，如果父类不存在选项，则返回子类选项，子类父类都存在时，用子类选项去覆盖父类选项
Object.defineProperty() 可以用来精准添加或修改对象的属性，并且有俩种属性的描述符：
数据描述符
configurable：属性是否可删除，配置
enumerable：属性是否可枚举
value：属性值，默认undefined
writable：属性是否可读写
存取描述符：
configurable：属性是否可删除，配置
enumerable：属性是否可枚举
get：一个给属性提供getter的方法
set：一个给属性提供setter的方法
利用存取描述符中的getter/setter来进行数据监听，这也是响应式构建的雏形。
优点：兼容性比较好
缺点：无法对数组或者嵌套的对象进行代理检测。不管是通过arr.push还是arr[10]的方法添加的数据，因为数组所添加的索引值并没有预选加入数据拦截中，所以无法进行拦截处理。vue对数组的方法进行来重写。
Proxy是真正在语言层面对数据拦截的定义，Proxy针对目标对象会创建一个新的实例对象，并将目标对象代理到新的实例对象上，外界对原对象的访问，都必须通过这层代理进行拦截处理。
优点：基本可以监听所有的数据
缺点：对兼容性要求较高
对数据进行筛选过滤，通过initProxy方法实现对vm的代理，其中hasHandler函数中定义来has的钩子，用于拦截 propKey in proxy的操作，也可以用来拦截with语句下的作用对象。
触发时机：
在模版中使用了未定义的变量，例如<div>{{message}}</div>，而如果我们在模版中使用了未定义的变量，这个过程就被proxy拦截，并定义为不合法的变量使用。(这时是vue这一层的报错，如果在不兼容proxy的环境里就会成为js语言层面的错误)
用户定义的变量与vue内置的变量冲突时。
基本上拥有除去 编译器 外的完整代码
runtime only只使用俩种场景
手写render函数去定义渲染过程
借助vue-loader这样的编译工具进行编译，比如平时利用template标签去书写代码，此时vue将编译工作交给了插件实现。
很明显编译过程对性能会造成一定的损耗，实际开发中我们需要借助vue-loader这类工具进行编译，将vue对模版的编译阶段合并到webpack的构建流程中，这样可以减少生成环境代码体积，也可以提高运行时的性能
可以使用 template 进行模版编写
编译器会自动将模版字符串编译成render函数
一句话概括：确认挂载节点，编译模版为render函数，渲染函数转换为Virtual DOM，创建真实节点。
确定挂载的节点，不能是body，html类似的根节点，一般是我们设置的#app节点。
如果是template模版字符串的写法，会先经过模版的解析，成为render函数，手写render函数会绕过这个解析过程，最终都会调用$mount方法
针对 template 而言，会利用内部的编译器进行模版的编译，字符串模版会转换成抽象的语法树，即AST树，最终转换成类似 fuction(){width(){}}的渲染函数
进入mountComponent过程，实例化一个渲染watcher，渲染watcher的回调函数有俩个执行时机，一个在初始化时执行，一个是当vm实例检测到数据发生变化时再次执行回调函数。这个回调函数是执行updateComponent的过程，该方法有俩个阶段，一个是vm._render，负责生成render渲染函数，生成虚拟DOM。一个是vm._update，会将虚拟DOM转换成真实的DOM节点。
template 有三种写法，
字符串模版
选择符匹配元素的 innerHTML模版
dom元素匹配元素的innerHTML模版
模版编译 的前提需要对template模版字符串的合法性进行检测，三种写法对应代码的是那个不同分支。
不同平台对vue的编译过程是不一样的，基础的编译方法会随着平台的不同有区别，编译的配置选项因为平台的不同呈现差异。
虚拟DOM作为JS和真实DOM中间的一个缓冲层，对JS频繁操作DOM的引起的性能问题他有很好的缓解作用。
虚拟DOM(Virtual DOM)是将页面的状态抽象为JS对象的形式。
Virtual DOM是Vue使用VNode此构造函数去描述一个DOM节点，不包含操作DOM的方法，只用来单纯描述节点的关键性能。
VNode有三种：注释节点，文本节点，普通节点。
Virtual DOM是在实例$mount方法中通过调用vm._render()方法创建的，_render方法在renderMixin中定义，核心代码为
`vnode = render.call(vm._renderProxy, vm.$createElement)`
vm._renderProxy是用来做数据过滤检测的，同时也绑定了render函数执行时的this指向。
vm.$createElement作为render方法的参数传入，也就是平时写代码时render函数中使用到的h方法。
vm.$createElement在初始化中的initRender函数中心定义。还定义了一个vm._c方法
vm._c是template内部编译成render函数时调用的方法，vm.$createElement是手写render函数时调用的方法，俩者唯一的区别是调用createElement方法时最后一个传参不同。前者能保证子节点都是VNode，而后者则需要一下检测和转换。
createElement方法在创建VNode时会对用户手写render函数传入的数据进行检测，比如以下几种错误
用响应式对象做data数据。
当特殊属性key的值为非字符串，非数字类型时。
Virtual DOM tree是由每个VNode以树状形式拼成的虚拟DOM树，所以必须保证每个子节点都是VNode类型，由于用户的render函数传入的参数是不确定的，所以会对子节点进行规范化。
在_render过程中会遇到子组件，会优先去对子组件进行处始化。
步骤案例
template 模版形式
var vm = new Vue({
el: '#app',
template: '<div><span>virtual dom</span></div>'
})
模版编辑生成了render函数
(function() {
with(this){
return _c('div',[_c('span',[_v("virual dom")])])
}
})
Virtual DOM tree的结果(省略版)
{
tag: 'div',
children: [{
tag: 'span',
children: [{
tag: undefined,
text: 'virtual dom'
}]
}]
}
在$mount方法中，_update方法将虚拟DOM映射成为真实的DOM，_update作为渲染Watcher的参数之一，所以有俩个调用时机，一个发生在初次渲染的阶段，一个发生在数据更新阶段。
_update的核心是__patch__方法，如果是服务端渲染，由于没有DOM，__patch__方法是个空函数，浏览器环境下，__patch__方法是patch函数的引用，patch函数是createPatchFunction方法的引用。
createPatchFunction的对象参数中，nodeOps是操作DOM对象的一系列方法，根据platforms平台不同，传入的配置也不同，modules定义了模块的钩子函数。
createPatchFunction的核心就是通过调用nodeOps配置里操作真实DOM的方法来生成真实DOM，比如调用createElm方法创建节点，插入字节点，经过递归创建后成为一个个完成的DOM树并插入body中，并且在产生真实DOM的阶段，会有diff算法来判断前后VNode的差异，以求最小化改变真实阶段。
全局组册：Vue.component
在Vue源码引入阶段时定义，具体位置在core/global-api/assets.js
俩个参数，id和组件选项，没有传组件选项的时候会直接返回组册过的以id为名的组件选项。
经过组件名的合法性检测后，调用Vue内置的extend方法为组件创建一个基于Vue的子类构造器，并将组件的信息加载到实例options.components对象中，并且会覆盖同名的组件。
局部组册
在执行vm.$mount(vm.$options.el)实例挂载的过程逻辑中，执行_render方法生成Vnode的阶段，如过判断节点标签为组册过的组件名标签名，会通过createComponent方法创建子组件Vnode。createComponent方法最终会通过new Vue实例化一个名称以vue-component-开头的Vnode节点，并且其中关键步骤是配置合并和安装钩子函数。
局部注册和全局注册的区别
局部注册添加的对象配置是在某个组件下的，而全局注册添加的子组件是在根实例下。
局部注册添加的是一个子组件的配置对象，而全局注册添加的是一个子类构造器。而局部注册构建子类构造器的过程在createComponent的源码里，在创建子Vnode时会调用父类的extend方法去创建一个子类构造器。
经过vm.render()生成完整的Virtual Dom树后，执行vm.update()执行渲染真实DOM的过程中，核心方法vm.__path__内部会通过过createElm去创建真实DOM，期间遇到子Vnode会递归调用createElm方法。
递归过程中，通过createComponent方法判断是否为组件类型(这里通过判断是否有钩子函数确定是不是组件类型)，此方法和渲染Vnode阶段的方法createComponent不同，他会调用子组件的init初始化钩子函数，并完成组件的DOM插入。
init初始化钩子函数的核心是new实例化这个子组件并将子组件进行挂载，实例化子组件的过程又回到合并配置，初始化生命周期，初始化事件中心，初始化渲染过程，实例挂载又会执行$mount过程
完成所有子组件的实例化和节点挂载后，最后才回到根节点的挂载。
通过vm.$parent拿到父实例，也可以在父实例中通过vm.$children拿到实例中的子组件。
无论是父实例还是子实例，在初始化实例即可端有一个initLifecycle的过程，这个过程会把当前实例添加到父实例的$children属性中，并设置自身的$parent属性指向父实例。
抽象组件指<keep-alive><slot>等，vue有很多内置抽象组件，这些抽象组件不会出现在你子父级的路径上，并且它们不会参与DOM的渲染。
全局组件需要通过Vue.compoent方法进行全局组册，并且需要在根组件实例化之前进行声明组册，原因是我们需要在实例化前拿到组件的配置信息并合并到options.components选项中。
注册的本质是调用extend创建一个子类构造器，全局和局部的不同是局部创建子类构造器是发生在创建子组件Vnode阶段。而创建子Vnode阶段最关键的一步是定义了很多内部使用的钩子。有一个完整的Vnode tree接下来会进入真正DOM的生成，在这个阶段如果遇到子组件Vnode会进行子构造器的实例化，并完成子组件的挂载，递归完成子组件的挂载后，最终回到根组件的挂载。
实例挂载前有一个重要的过程，数据初始化，即initState。
initState过程是对数据进行响应式设计的过程，过程会针对props，methods，data，computed，watch做数据的初始化处理，并将它们转换为响应式对象
父组件对传递props的处理：
父组件优先进行模版编译得到一个render函数，在解析过程中遇到子组件的属性 :test=test会被解析成{attrs:{test:test}}并作为子组件的render函数存在。
render解析Vnode的过程中碰到子组件，会进入创建子组件Vnode的过程。对于props的处理，会使用extracPropsFromVNodeData方法对attrs进行规范校验，并把校验结果以propsData属性传入Vnode构造器中。
extracPropsFromVNodeData校验具体过程：props编译后的结果有俩种，其中attrs是编译生成render函数针对属性的处理，而props是针对用户自写render函数的属性值。因此需要同时对这俩种方式进行校验。因为浏览器对大小写是不敏感的，所以这里会把模版中使用驼峰命名法的props转换为短横线分割命名。
子组件对props的处理：
子组件处理的过程，发生在父组件_update的阶段。此阶段是Vnode生成真实节点的过程，期间会遇到子Vnode，调用createCompoent去实例化子组件。实例化的过程中又回回到_init初始化，并且经历选项的合并，调用initProps，而
initProps做的事情，就是将组件的props数据设置为你响应式数据。
其中proxy(vm，"_props"，key)，就是为props做了一层低矮里，用户通过vm.XXX可以代理访问到vm._props上的值。
与响应式无关，实现简单，主要保证methods 方法必须是函数，且命名不能和props重复，不能以_ or $.这些Vue保留标志开头。
最终挂载在当前实例的根上，通过vm[key]访问。
data在初始化选项合并时会生成一个函数，只有执行函数时才返回真正的数据。
initData会对对象每个属性进行校验，不能和props，methods重复。
核心方法是observe，observe方法将数据对象标记为响应式对象，并对对象的每个属性进行响应式处理。具体行为是将数据添加一个不可枚举的属性__ob__，标志对象是一个响应式对象，并且拿到每个对象的属性值，重写getter，setter方法，使得每个属性值都是响应式数据。
同时通过proxy函数做一层代理，直接通过vm.XXX可以代理访问到vm._data上挂载的对象属性。
initComputed是computed数据的初始化。
computed可是对象，也可以是函数，当时为对象是必须要有getter方法，因此为对象时需要进行验证。
针对computed的每个属性，要创建一个监听依赖，实例化一个watcher。
initComputed同样会将每个属性设置成响应式数据，也会做命名检测，防止与props，data冲突
Observer类，实例化一个Observer类会通过Object.defineProperty对数据的getter，setter方法进行改写，在getter阶段进行依赖的收集(通过dep.depend方法)，在数据发生更新阶段，触发setter方法进行依赖的更新(通过dep.notify方法)。
Watcher类，实例化Watcher类相当于创建一个依赖，当数据发生改变时，会通知到每个依赖进行刚更新，前面提到的渲染watcher便是渲染dom时使用数据产生的依赖。
Dep类，既然Watcher理解为每个数据需要监听的依赖，那么对这些依赖的收集和通知则需要另一个类来管理，这个类便是Dep，Dep需要做的只有俩件事，收集依赖和派发更新依赖。
data在初始化阶段(initData)，会初始化一个Observer类。(以下忽略了数组类型的data)
Observer类会为data添加一个__ob__属性，此属性是作为响应式对象的标志。同时def方法确保了此属性是不可枚举属性。并且调用了原型上的walk方法，遍历对象上每个属性进行getter，setter的改写。
walk方法的核心为defineReactiive方法，首先会实例化一个Dep类，即为每个数据都创建一个依赖的管理，如果遇到深层次对象(属性为一个对象)，则会递归调用实例化Observer类，让其属性值也转换为响应式对象，并且创建一个Dep类。接下来会利用Object.defineProperty重写getter，setter方法。
当data属性值被访问时：就会被getter函数拦截。实例挂载之前会创建一个渲染watcher，并且同时 updateCompoent的逻辑会执行实例的挂载，此过程中，模版会被优先解析为render函数，而render函数转换成Vnode时，会访问到定义的data数据，此时就会触发getter进行依赖的收集，数据收集的依赖就是这个渲染watcher本身(指的是Dep.target)。
所以依赖收集阶段主要做下面2件事
为当前的watcher添加拥有的数据。(Dep.target.addDep(this))
为当前的数据收集需要监听的依赖。(dep.addSub(this))
当data数据被改变时：触发定义好的setter方法，主要做了以下几件事
判断数据更改前后是否一致，如果数据相等则不进行任何派发更新操作。
新值为对象时，会对该值的属性进行依赖收集过程
通知该数据收集的watcher依赖，遍历每个watcher进行数据更新(调用dep.notify方法进行更新的派发，该方法内通过调用watcher类的update方法进行更新数据操作)
更新时将每个watcher推到队列中，等待下一个tick(事件循环)到来时取出每个watcher进行run操作。
watcher类中的update方法核心是queueWatcher方法，该方法会将数据所收集到的依赖依次推到queue数组中，数组会在下一个事件循环‘tick’中根据缓冲结果进行视图更新。
queueWatcher方法最终会调用nextTick方法，nextTick会缓冲多个数据处理过程，等到下一个事件循环tick中再去执行DOM操作，原理是利用事件循环的微任务队列实现异步更新。
tick到来时会执行flushSchedulerQueue方法，它会拿到收集的queue数组(一个watcher集合)，并对数组依赖进行排序，排序的原因如下：
组件创建是先父后子，所以组件的更新也是先父后子。因此需要保证父的渲染watcher优先于子的渲染watcher更新。
用户自定义的 watcher 称为 user watcher。user watchers比render watcher要先创建，所以user的要优先执行。
如果一个组件在父组件的watcher执行阶段被销毁，那么它对应的watcher都可以跳过。
flushSchedulerQueue过程总结如下四点
对queue中的watcher进行排序
遍历watcher，如果当前watcher有before配置，执行before方法。比如渲染watcher实例化时就传递了before函数，意思为在下一个tiick更新视图前，调用beforeUpdate生命周期函数
执行watcher.run 进行修改的操作
重置恢复状态，清空watcher队列
watcher.run() 的操作：执行watcher.prototype.get方法，得到数据变化后的值，对新值进行判断，满足条件执行cb，cb为实例化watcher时传入的回调，渲染watcher传的是一个空函数noop。
get方法会执行this.getter进行求值，在当前渲染watcher的条件下，getter会执行视图更新的操作，这一阶段会重新渲染页面组件。执行完getter方法后，最后一步进行依赖的清除（cleanupDeps方法）。
依赖清除的作用例子：使用v-if进行模版的切换时，如果A模版监听a数据，B模版监听b数据，当切换渲染到B模版时，如果不进行旧依赖的清除，a数据发生了变化同样会很引起A模版的重新渲染更新，造成性能浪费。
依赖派发更新小结：
执行run操作会执行getter方法，重新计算新值，而针对渲染watcher而言，会重新执行updateComponent进行视图更新
重新计算getter后，会进行依赖的清除。
computed的初始化过程，会遍历computed的每一个属性值，并且为每一个属性实例化一个computed watcher，其中{lazy: true}是computed watcher的标志。
设置属性为响应式数据的过程和data的逻辑相似，最终调用Object.defineProperty进行数据拦截。
在非服务端渲染的情形下，计算结果会被缓存，只有在响应式数据发生变化时，computed 才会重新求值。执行createComputedGetter方法。
createComputedGetter中会先拿到属性的computed watcher，dirty是标志是否已经执行过计算结果，如果为false，代表已经执行过，就不会执行watcher.evaluate重复计算。
watcher.evaluate的作用就是调用get方法，并且设置dirty为false，同样会进行依赖的清除。get执行完毕后会进入 watcher.depend 进行依赖的收集。收集过程和data一致，将当前的computed watcher作为依赖收集到数据的依赖收集器dep中
计算属性派发更新的条件是data数据发生改变。步骤如下
当计算属性依赖的数据发生更新时，由于数据的Dep收集过computed watch这个依赖，所以会调用dep的notify的方法，对依赖进行状态更新
不会立刻执行依赖的更新操作，通过一个dirty设置为true进行标记
由于data数据拥有渲染watcher这个依赖，所以同时会执行updateComputed进行视图重新渲染，而render过程中会反问到计算属性，此时由于dirty在上一步设置为true，所以会对计算属性重新求值。
Object.defineProperty的get，set方法只能检测到对象属性的变化，对数组的变化无能为力。
Vue在保留愿数组功能的前提下，重新定义了数组部分方法(主要是增删改查的方法，push,pop,shift,unshift,splice,sort,reverse)。
在initData阶段，为每个data数据创建一个Observer类的时候，会对数组类型单独处理，通过判断当前环境是否支持__proto__属性来执行不同的方法。支持时，执行protoAugment会将当前数组的原型指向新的数组类arrayMethods。不支持时，执行copyAugment通过代理访问指向数组类arrayMethods。
在defineReactive进行数据访问的改写阶段，childOb判断是否为引用类型的数据，不是就直接返回value，是则会为每个子属性设置响应式数据，dependArray保证了如果数组元素是数组或者对象时，能递归去为内部的元素收集相关的依赖。
开发中调用数组的方法时会代理到新类 arrayMethods的方法中，其中mutator是重写的数组方法，首先会调用原始的数组方法进行运算，保证与原始数组类型的方法一致。之后取出__ob__(Observer实例)，调用ob.dep.notiify()进行依赖的派发更新。通过inserted来标志数组是否增加了元素，如果增加的元素时候数组对象类型，则触发observeArray方法对每个元素进行依赖收集。
小结：原始数组的改变不会触发setter进行依赖更新，所以Vue创建来一个新的数组类，重写类数组的方法，将数组方法指向了新的数组类，同时在访问到数组时依旧触发getter进行依赖收集，在更改数组时，触发数组新方法运算，并进行依赖的派发。
但即使重写了数组的方法，不管是通过索引还是直接修改长度，都还是无法触发依赖更新的，检测不到数组的变动。
Vue提供的set方法和vm.$set方法的内部实现分为4个不同的分支
目标对象必须为非空的对象，可以是数组，否者抛出异常
如果目标对象是数组时，调用数组的splice方法，进而调用ob.observeArray(inserted)对数组新增的元素收集依赖
新增的属性值在原对象已经存在，则手动的访问该属性值，此操作会触发依赖收集
新的属性值在原对象不存在时，手动定义新属性的getter，setter方法，并通过notify触发依赖更新
事件循环机制有俩种异步队列：
macro-task（宏任务）：常见的的有setTimeout、setInterval、setImmediate、script脚本、I/O操作、UI渲染
micro-task（微任务）：常见的有promise、process.nextTick、MutationObserver
完整事件循环流程为：
微任务队列为空，宏任务队列只有script脚本，推出宏任务的script任务执行，脚本执行期间产生的微任务，宏任务推到对应的队列中。
执行微任务队列的所有微任务事件
执行DOM操作，渲染更新页面
执行web worker等相关任务
循环，取出宏任务中中的下一个宏任务事件执行，重复以上过程。
从以上流程可以发现，最好的渲染过程发生在微任务队列的执行过程中，因为此时离页面渲染过程最近。比如Vue是数据驱动视图更新渲染的，一个循环正宗执行this.num++一千次，只需要关系最后第一千次在界面上更新的结果而已。
基本实现：Vue用一个queue收集依赖的执行，在下次微任务执行的时候统一执行queue中Watcher的run操作，与此同时，相同id的watcher不会重复添加到queue中，因此也不会重复执行多次的视图渲染。
nextTick不传任何参数时，可以作为一个promise使用，例如：nextTick().then(() => {})
nextTick函数的实现中，timerFunc是真正将任务队列推到微任务队列中的函数。有以下几种情况
浏览器支持Promise，默认以Promise将执行过程推到微任务队列中。flushCallbacks是异步更新的函数，他会取出callbacks数组的每一个任务，执行任务。
不支持promise，支持MutataionObserver。
不支持微任务方法，则会使用宏任务方法，setImmediate会先被使用
所有方法都不适合，会使用宏任务方法中的setTimeout。
nextTick的使用场景：由于异步更新的原理，我们在某一时间改变的数据并不会触发视图的更新，而是需要等下一个tick的到来时才会更新视图。
Vue在更新DOM时是异步执行的，只要侦听到数据变化，Vue将开启一个队列，并缓冲在同一事件循环中发生的所有数据变更。
执行initState初始化数据时，当传入的选项里有watch选项时，会执行initWatch，其核心为createWatcher。
无论传入的是选项的形式，还是api的形式，函数的形式，最终都会调用实例的$watch方法，$watch的核心是创建一个user watcher，其中options.user是当前用户定义watcher的标志，如果有immediate属性，则立即执行回调函数。而实例化watcher的时候，会执行一次getter求值，此时 user watcher会作为依赖被数据所收集，过程类似data。
watch的派发更新就是数据发生改变时，setter拦截对依赖进行更新，而此时user watcher已经被当成依赖收集了。此时依赖的更新就是回调函数的执行。
Vue在渲染机制的优化上，利用Virtual DOM描述一个真实的DOM，本质在JS和真实DOM之间架起了一层缓冲层。
从render函数到创建虚拟DOM，再到渲染真实节点，这一过程是完整的，也是容易理解的，然而引入虚拟DOM的核心不在这里，而在于数据发生变化时，如何最优化数据变动到视图更新的过程，这一个过程才是Vnode更新视图的核心，也就是常说的diff算法。
：
数据的改变最终会导致节点的改变，所以diff算法的核心在于尽可能小变动的前提下找到需要更新的节点，调用原生相关DOM方法修改视图。算法比较节点不同时，只会进行同层节点的比较，不会跨层进行比较，这也大大减少了算法复杂度。
新旧节点的对比是算法的第一步，如果新旧节点的根节点不是同一个节点，则直接替换节点。(只进行同层节点的比较，节点不一致，直接用新节点及其子节点替换旧节点)
进行节点对比的核心逻辑如下
节点相同，且节点除了拥有文本节点外没有其他子节点，直接替换文本内容。
新节点没有子节点，旧节点有子节点，则删除旧节点所有子节点。
旧节点没有子节点，新节点有子节点，则用新的所有子节点去更新旧节点。
新旧都存在子节点，则对比子节点内容做操作。
对比子节点内容比较复杂。简单概括就是新旧节点 头尾交互对比。对比顺序为：新头—老头。新尾—老尾。新尾—老头。新头—老尾。
通过对比会不断更新 新老头尾的下标，当老尾下标<=老头下标，说明旧节点已经遍历完了，此时只要批量增加新节点即可。 当新尾下标<=新头下标，说明旧节点还有剩下，此时只要批量删除旧节点即可。
diff算法优化：当四种比较节点都找不到匹配时，会调用findIdxInOld找到旧节点中和新节点一致的节点。 我们在编写列表时被要求加入的唯一属性key，有了这个唯一的标志位，我们可以对旧节点简历简单的字典查询，只要有key值便可以方便的搜索到符合要求的旧节点。
Virtual DOM的重绘性能真的比单纯的innerHTML要好嘛？并不是的。
innerHTML:render html string 0(template size) + 重新创建所有DOM元素
Virtual DOM:render Virtual DOM + diff 0(template size) + 必要的DOM更新
Virtual DOM render + diff 显然比渲染html字符串要慢，但是，它依然是纯js层面的即计算，比起后面的DOM操作来说，依然便宜了太多。可以看到，innerHTML的总计算量不管是js计算还是DOM操作都是和整个界面的大小相关，但Virtual DOM的计算量里面，只有js计算和界面大小相关，DOM操作是和数据的变动量相关的。
vue是如何对事件绑定做处理的呢？
vue在挂载实例前，有相当多的工作是进行模版的编译，将template模版进行编译，解析成AST树，再转换成render函数，有了render函数后才会进入实例挂载阶段。
对事件的第一步处理，就是在编译阶段对事件指令做收集处理。
模版编译的入口是在var ast = parse(template.trim(),options);中。parse通过拆分模版字符串，将其解析为一个AST树，其中对属性的处理，在processAttrs中。
processAttrs的逻辑虽然较多，但是理解起来比较见简单，通过正则/^v-|^@|^:/，命中的结果会得到事件指令相关内容。通过addHandler方法，为AST树添加相关的属性，并且对事件修饰符进行特殊处理。
模版编译的最后一步是根据解析完的AST树生成对应平台的渲染函数，也就是render函数的生成过程，对应var code=generate(ast,options);其中核心处理在getElement中，getElement函数会根据不同指令类型处理不同的分支，对于普通模版的编译会进入genData函数中处理，其中genHandlers函数会为你event属性做逻辑处理。
genHandlers的逻辑会遍历解析好的AST树，拿到event对象属性，并根据属性上的时间对象拼接成字符串。
模版中事件的三种写法都有相应正则对应。
<div @click="doThis"></div>
<div @click="doThis($event)"></div>
<div @click="()=>{}"></div>
如果事件不带任何修饰符，且满足正确的模版写法，则直接返回调用事件名。如果不满足则会封装到function($event){}中。
如果有修饰符，会通过modifierCode中找到对应的脚本，加入函数字符串中返回。
经过这一转换后，生成 width 封装的 render 函数如下
"_c('div',{attrs:{"id":"app"}},[_c('div',{on:{"click":function($event){$event.stopPropagation();return doThis($event)}}},[_v("点击")]),_v(" "),_c('span',[_v(_s(count))])])"
模版通过解析生成AST树，然后AST树构建成render函数后，自然可以生成实例挂载需要的Vnode树，在进行实例挂载，通过patchVnode进行真实节点构建的过程中会对事件进行注册相关处理。
在遍历子节点递归调用createElm为每个子节点创建真实DOM时，因为事件绑定放在Vnode的data属性里，会通过invokeCreateHooks来进行注册事件相关处理。
我们经常在template模版中定义v-on事件，v-bind动态属性，v-text动态指令等，和v-on事件指令一样，会在编译阶段和Vnode生成阶段创建data属性。因此invokeCreateHooks就是一个模版指令处理的任务，针对不同的指令创建不同的任务，针对事件，调用updateDOMListeners对真实DOM节点注册事件任务。
其中 normalizeEvents 是针对 v-model的处理，例如咋IE下不支持change事件，只能用input事件代替。updateListeners会遍历on事件对新节点事件绑定注册事件，对旧节点移除事件监听，既要处理原生DOM事件的添加和移除，也要处理自定义事件的添加和移除。
add和remove方法是真正在DOM上绑定事件和解绑事件的过程，它们的实现也是利用了原生DOM的 addEventListener，removeEventListener api。
父子组件可以利用自定义事件进行通信，因此针对自定义事件在源码中自然有不同的处理逻辑。
在事件的模版编译，生成AST树阶段啊，addHandler方法会对事件的修饰符做不同的处理，当遇到native修饰符时，事件相关属性方法会添加到nativeEvents属性中。
经过genData中的genHandlers处理后，会生成render函数，接下来创建Vnode实例，其中遇到组件占位符节点时会创建子组件Vnode，此时为on，nativeOn做了一层特殊的转换，将nativeOn赋值给on，这样后续的处理方式和普通节点一致，另外将on赋值给你listeners，在创建VNode时以组件配置componentOptions传入。
接下来通过Vnode生成真实节点的过程，这个过程遇到子Vnode会实例化子组件实例。此时，子组件拿到父占位符节点定义的@myevent="myevent"事件，带有自定义事件的组件会执行updateComponentLiisteners函数。之后又回到之前分析的updateListeners过程，和原生DOM事件不同的是，自定义事件的添加移除的方法不同。
Vue在引入阶段通过eventMixin对事件的处理，用一个_events 属性存储管理事件的派发和更新，暴露出$on,$once,$off,$emit方法给外部管理事件和派发执行事件。组件通过this.$emit在组件实例中派发了事件，而在这之前，组件已经将需要监听的事件以及回调添加到实例的_events属性中，触发事件时便可以直接执行监听事件的回调。
组件自定义事件的触发和监听本质上都是在当前组件实例中进行，之所以能产生父子组件通信的效果是因为事件监听的回调函数写在了父组件中。