学习笔记:Vue2.X源码

215 阅读16分钟

Vue源码(未完成)

1.准备工作

1.1目标

  • vue.js的静态成员和实例的初始化过程

  • 首次渲染过程的原理

  • 响应式的原理

1.2准备工作

  • 下载源码git clone https://github.com/vuejs/vue.git

  • 安装依赖

  • 开启代码地图设置"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"

  • npm run dev

1.3构建不同版本的vue

UMDCommonJSES Module
FULLvue.jsvue.common.jsvue.common.js
Runtime-onlyvue.runtime.jsvue.runtime.common.jsvue.runtime.esm.js
Full(production)vue.min.jsvue.common.prod.js
Runtime-only(production)vue.runtime.min.jsvue.runtime.common.prod.js
  • 名词解释
    • Full:这是一个全量的包,包含编译器(compiler)和运行时(runtime
    • Compiler:编译器,负责将模版字符串(即你编写的类 html 语法的模版代码)编译为 JavaScript 语法的 render 函数。
    • Runtime:负责创建 Vue 实例、渲染函数、patch 虚拟 DOM 等代码,基本上除了编译器之外的代码都属于运行时代码。
    • UMD:兼容 CommonJS 和 AMD 规范,通过 CDN 引入的 vue.js 就是 UMD 规范的代码,包含编译器和运行时。
    • CommonJS:典型的应用比如 nodeJS,CommonsJS 规范的包是为了给 browserify 和 webpack 1 这样旧的打包器使用的。他们默认的入口文件为 vue.runtime.common.js
    • ES Module:现代 JavaScript 规范,ES Module 规范的包是给像 webpack 2 和 rollup 这样的现代打包器使用的。这些打包器默认使用仅包含运行时的 vue.runtime.esm.js 文件。

1.4寻找入口文件

  • 执行构建

    npm run dev
    # "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
    #  TARGET:web-full-dev // 打包的版本
    
  • 查看scripts/config.js文件

    • 生成完整版的时候地址src/platforms/web/entry-runtime-with-compiler.js'

1.5目录结构

├── benchmarks                  性能、基准测试
├── dist                        构建打包的输出目录
├── examples                    案例目录
├── flow                        flow 语法的类型声明
├── packages                    一些额外的包,比如:负责服务端渲染的包 vue-server-renderer、配合 vue-loader 使用的的 vue-template-compiler,还有 weex 相关的
│   ├── vue-server-renderer
│   ├── vue-template-compiler
│   ├── weex-template-compiler
│   └── weex-vue-framework
├── scripts                     所有的配置文件的存放位置,比如 rollup 的配置文件
├── src                         vue 源码目录
│   ├── compiler                编译器
│   ├── core                    运行时的核心包
│   │   ├── components          全局组件,比如 keep-alive
│   │   ├── config.js           一些默认配置项
│   │   ├── global-api          全局 API,比如熟悉的:Vue.use()、Vue.component() 等
│   │   ├── instance            Vue 实例相关的,比如 Vue 构造函数就在这个目录下
│   │   ├── observer            响应式原理
│   │   ├── util                工具方法
│   │   └── vdom                虚拟 DOM 相关,比如熟悉的 patch 算法就在这儿
│   ├── platforms               平台相关的编译器代码
│   │   ├── web
│   │   └── weex
│   ├── server                  服务端渲染相关
├── test                        测试目录
├── types                       TS 类型声明

2.Vue初始化过程

2.1从入口开始

  • 通过查看代码判断以下输出结果

    <body>
        <div id="app"> </div>
        <script src="../../dist/vue.js"></script>
        <script>
            const vm = new Vue({
            el:'#app',
            template:'<h2>haha</h2>',
            render(h) {
      	        return h('h3','xixi')
    	        }           
            })
        </script>
    </body>//最终输出xixi
    
  • 源码笔记

    • 传入的el不能Body或则html标签

    • 如果没有传入render,把template转换成render函数

    • 如果有render方法直接,调用mount挂载DOM

// $mount方法是是把把生成的dom挂载到页面上 
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

// vue不能挂载到body或则html上
if (el === document.body || el === document.documentElement) {
  process.env.NODE_ENV !== 'production' && warn(
    `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
   )
  return this
}

const options = this.$options
if (!options.render) { // 如果没有传入render函数就把template转化成render函数
 	// ---------   
}
// 调用mount方法渲染dom
return mount.call(this, el, hydrating)
Vue.compile = compileToFunctions
  • 代码调试 从call stack中可以看到的调用位置

2.2Vue导出模块

从四个导出vue模块查看初始化流程

  • \src\platforms\web\entry-runtime-with-compiler.js

    • Web平台相关入口
    • 主要是处理导出完整版的代码
    • 重写$mount的方法
    • 挂载vue.compiler()
    • 引入Vueimport Vue from './runtime/index'
  • vue\src\platforms\web\runtime\index.js

    • Web平台相关入口
    • 在vue.config内挂载一些内部使用的方法
    • 调用extend方法,注册一些组件和指令
      • 导入组件 Transition,TransitionGroup
      • 导入指令show,model
    • 在浏览器的环境下挂载patch方法,
      • patch可以借鉴snabbdom的patch方法,用来对比新旧两个虚拟dom
      • inBrowser判断条件,export const inBrowser = typeof window !== 'undefined'
    • 给vue中挂载$mount()
  • vue\src\core\index.js

    • 通过initGlobalAPI全局注册一些方法
  • vue\src\core\instance\index.js

    • 与平台无关
    • 创建Vue搞糟函数
    • 定义了构造函数,调用了 this._init(options)
    • 给Vue中混入了常用的实例成员

2.3 调试初始化过程

  • 准备工作
<!-- 准备一份基础代码 -->
<body>
   <div id="app">
       {{ msg }}
   </div>
   <script src="../../dist/vue.js"></script>
   <script>
       const vm = new Vue({
       el:'#app',
       data: {
           msg: 'Hello Vue'
       }
       })
   </script>
</body>

分别设置四个断点

vue初始化调试断点.png

vue初始化调试断点2.png

vue初始化调试断点3.png

vue初始化调试断点4.png

  • 开始调试

    • F5刷新进入src/core/instance/index.js文件(平台无关),该文件作用式给Vue原型挂载上一些方法
    • 在watch中监视Vue
    • F10执行函数会添加相对应的方法
      • 其中_x的方法实在模板编译的时候使用

initmix.png

  • 初始化静态成员

    • F8进入下一个文件,调转到initGlobalAPI(Vue)函数中,该函数作用是给vue实例上挂在一些静态成员

    • F11进入该函数

QQ截图20220104004841.png

  • 导入平台相关代码

    • F8进入到src/platforms/web/runtime/index.js文件中
    • 注册平台对应的方法
    • 添加了组件和指令
    • 挂载了_patch和$mount方法到原型上

tiaoshi5.png

  • 入口文件
    • 重写了原型上的$mount,使其可以进行模板编译
    • 注入Vue.compile方法,该方法的作用手动把模板编程render函数

2.4 源码解读

2.4.1 Vue(options)

  • 路径src\core\instance\index.js

  • 作用

    • 定义Vue的构造函数,初始化执行相关函数
// 此处不用class的原因是应为方便后面给Vue实例混如实例成员
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
// 注册vm的_init()方法初始化vm
initMixin(Vue)
// 注册vm的$data/$props/$set/$delete/$watch
stateMixin(Vue)
// 初始化事件相关的方法$on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _updata/$forceUpdata/$destroy
lifecycleMixin(Vue)
// 混入render
// $nextTick/_render
renderMixin(Vue)

2.4.2 Vue.prototype._init

  • 路径src\core\instance\init.js

  • 作用

    • 定义注vm的_init()方法
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    // vm 用来记录当前vue实例
    const vm: Component = this
    // a uid 
    vm._uid = uid++ // 标识符

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    // 标志这个当前实例是vue实例,将来定义响应式数据的时候不对他经行处理
    vm._isVue = true
    // merge options 合并options
    // 两个方法都用是吧用户传入的options和vm的构造函数的options合并
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */ 
    // 设置渲染代理,主要是在渲染过程中的时候用,
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm) // 判断及初始化的过程
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化生命周期相关的变量
    // $children/$parent/$root/$refs
    initLifecycle(vm)
    // 初始化当前组件的事件,将父组件绑定在当前组件的事件上
    initEvents(vm)
    // vm的编译render初始化
    // $slots/$scopedSlots/_c/$createdElemnet/$attrs
    initRender(vm)
    // 触发声明周期的钩子,这里式beforeCreate
    callHook(vm, 'beforeCreate')
    // 把inject的成员注入到vm上
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    // 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上  依赖注入
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

2.4.3 initInternalComponent()

  • 路径src\core\instance\init.js

  • 作用

    • 对组件进行优化处理
    • 对组件选项进行打平做性能优化,当组件有嵌套很多层,避免免不了要通过原型链进行动态查找,影响执行效率。
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  // 根据vm的构造函数创建新的配置$options对象,即
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode
    
 // 把当前组件配置项打平然后赋值到 $options 对象,避免了原型链的动态查找	
  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  //如果当前组件配置项中存在render选项,就把它添加到 $options对象上
  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

2.4.4 resolveConstructorOptions()

  • 路径src\core\instance\init.js

  • 作用

    • 从组件构造函数中解析配置对象 options,并合并基类选项。
export function resolveConstructorOptions (Ctor: Class<Component>) {
  // 配置项目
  let options = Ctor.options
  // 如果构造函数的super属性存在,存在基类,递归解析基类构造函数的选项
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // 说明基类构造函数选项已经发生改变,需要重新设置
      Ctor.superOptions = superOptions
      // 检查 Ctor.options 上是否有任何后期修改/附加的选项
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // 如果存在被修改或增加的选项,则合并两个选项
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      // 选项合并,将合并结果赋值为 Ctor.options
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

2.4.5 mergeOptions()

  • 路径src\core\util\options.js

  • 作用

    • 合并两个选项,出现相同配置项时,子选项会覆盖父选项的配置
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  // 标准化 props、inject、directive 选项,入参的时候可以数组或对象
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // 处理原始 child 对象上的 extends 和 mixins,分别执行 mergeOptions,将这些继承而来的选项合并到 parent
  // mergeOptions 处理过的对象会含有 _base 属性
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  const options = {}
  let key
  // 遍历 父选项
  for (key in parent) {
    mergeField(key)
  }

  // 遍历 子选项,如果父选项不存在该配置,则合并,否则跳过,因为父子拥有同一个属性的情况在上面处理父选项时已经处理过了,用的子选项的值
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }

  // 合并选项,childVal 优先级高于 parentVal
  function mergeField (key) {
    // strats = Object.create(null)
    const strat = strats[key] || defaultStrat
    // 值为如果 childVal 存在则优先使用 childVal,否则使用 parentVal
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

2.4.6 initGlobalAPI(vue)

  • 路径:src\core\global-api\index.js
  • 作用
    • initGlobalAPI 是初始化全局API的,它是先于new Vue执行的,提供全局方法在Vue的执行过程中执行相应的方法。
    • 定义vue.options
export function initGlobalAPI (Vue: GlobalAPI) {
  // config. 定义属性的描述符号
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  // 初始化vue.config对象
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  // 这些工具方法不视为全局Api的一部分,不用然会有风险
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  // 挂载静态方法Set/delete/nextTick、后面看
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  // 让一个对象可响应,后面看
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  // 初始化 vue.options对象,并且扩展'component','directive','filter'三个属性
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  //extend将builtInComponents复制到Vue.options.components
  // 导入keepalive组件
  extend(Vue.options.components, builtInComponents)

  // 注册Vue.use()用来注册插件
  initUse(Vue)
  // 注册Vue.mixin()用来注册混入
  initMixin(Vue)
  // 注册Vue.extend()基于传入的options返回一个组件的构造函数
  initExtend(Vue)
  // 注册vue.directive() VUE.component,vue.filter
  initAssetRegisters(Vue)
}

2.5调试Vue.init

  • 设置断点,Watch中监视vm

1641740105389.png

断点2.png

  • 在mergeOptions中合并了 原有的options和传入的options

mergeoptions.png

  • 在开发环境中F11进入到initProxy
    • 判断浏览器是否支持proxy给 vm._renderProxy赋值

vm._renderProxy .png

  • 设置断点vm.$mount,F11进入

$mpunt断点.png

  • 处理得到传入的template

获取template.png

template的内容.png

  • 运行到return mount.call(this, el, hydrating)F11进入函数
    • 这里会重在runtime/index.js中主要是重新获取el(处理运行时版本没有编译器)
    • 调用mountComponent函数,F11进入该函数

runtimejs.png

  • 在mountComponent中,每个组件都会创建一个new Watcher 设置断点进入到new Watcher

newWatcher.png

  • 在创建的观察者中,在get中触发getter的方法的时候就会渲染数据

首次渲染.png

2.6挂载数据相关源码

2.6.1Vue.prototype.$mount()

  • 位置:src\platforms\web\entry-runtime-with-compiler.js
    • 作用定义Vue.prototype.$mount()
// $mount方法是是把把生成的dom挂载到页面上 
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  // vue不能挂载到body或则html上
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  // 获取options处理得到对应的templiate
  if (!options.render) { // 如果没有传入render函数就把template转化成render函数
    let template = options.template
    if (template) {  // 如果模存在
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') { // 判断模板是id选择器
          template = idToTemplate(template) // 把id转化成DOM元素获取里面的innerHTML作为模板
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) { 
        // 如果模板是元素,返回元素的innerHTML
        template = template.innerHTML
      } else { // 都没有的话就抛出警告
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // 如果没有tempalte,获取el的outerHTML作为模板
      template = getOuterHTML(el)
    }

    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      // 将模板编译成render函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // 调用mount方法渲染dom,注意这里在runtime/index.js中会写获取el
  return mount.call(this, el, hydrating)
}

2.6.2mountComponent()

  • 路径:src\core\instance\lifecycle.js
  • 作用
    • 先判断 vm.$options.render 是否存在,如果不存在的话就让它等于 createEmptyVNode
    • 接着定义了 updateComponent 函数
    • 触发beforeMount生命周期钩子
    • 创建了一个渲染 Watcher
    • 触发beforeMount生命周期钩子
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 判断选项中有没有render函数
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  // 触发beforeMount生命周期钩子
  callHook(vm, 'beforeMount')

  // 跟新组件函数
  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      // 调用用户传入的render或编译器生成的render,并返回一个虚拟dom并返回给_updata
      // _updata就是把虚拟dom转化未真实dom
      // 执行到这里后会把模板渲染到节面上
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  // 创建一个Watcher对象
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  // 触发mounted生命周期钩子
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

2.7初始化过程思维导图

VUE首次渲染过程.png

3数据响应式原理

3.1从入口文件开始

  • 思考问题

    • vm.msg = { count : 0 },给属性中心赋值是否时响应式
    • vm.arr[0] = 4 ,给数组元素赋值,视图是否会更新
    • vm.arr.length = 0 ,修改数组的length,视图是否会更新
    • vm.arr.push(4),视图是否会更新
  • 响应式处理入口

    • 位置core\instance\state.js
if (opts.data) {
      initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  • 设置断点 在

3.2调试依赖收集过程

  • 准备工作
    • 收集依赖过程,就是收集模板Observe中使用到对应组件的Watcher添加到Dep的subs数组中,只要数据法发生变化就通知Watcher去更新视图
<body>
    <div id="app">
        <h1>{{msg}}</h1>

        {{msg}}

        <hr>

        {{count}}
    
    </div>
    <script src="../../dist/vue.js"></script>
    <script>
        const vm = new Vue({
        el:'#app',
        data: {
            msg: 'Hello Vue',
            count: 100
        }
        })
    </script>
</body>
  • 设置断点src\core\instance\lifecycle.jsmountComponent()创建的New Warcher

    • 其中知道关注的式这个updateComponen

new Watcher.png

  • 进入Wacher,并构造数内会执行自身的get方法,右边式函数执行过程,进入 pushTarget

watcherget.png

  • pushTarget中会把Watcher会把对象压入到一个栈中,且赋值给Deg.target

watcherpushtarget.png

  • 值得注意在Watcher.get.try中执行的getter指向的是updateCompent,而updateCompent内部调用了vm._update(vm._render(), hydrating),先是生成虚拟dom然后在生成真实DOM
  • 进入到vm._render()函数中这个最终会调用render()函数,render可由用传入或模板编译而成

vm-render.png

vmrender.png

  • 模板中F11进入到hasHandler函数中,该函数是用来判断vue实例中是否有 _ c、_ v、msg等成员

hashandler.png

  • 当判断是否有msg的时候,会触发proxyGetter, 访问_data中的msg

msgGet方法.png

  • 然后进入_data属性中的msg的get方法
    • 在这个get方法中会收集依赖,在watchet对象中已经给Dep.target赋值了所以进入Dep.depend方法内
    • depend()内会调用Dep.target.addDep(this),此时Dep.target指向就是原来的Watcher对象,this指向msg的dep对象
    • 在Watcher对象中的appdep就是判断,Dep.subs内是否有对应的Watcher,没有就那wancher对象添加到subs数组中,度过有就跳过

收集msg依赖.png

depend方法.png

3.3源码解读

3.3.1 initState(vm)

  • 路径src\core\instance\state.js
  • 作用
    • 初始化data,props,methods,watch,computed等
    • 注意:初始化的顺序很重要,这就标志着后面初始化的选项名称不能与已初始化的重复。
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

3.3.2 initProps(vm, opts.props)

  • 路径:src\core\instance\state.js
  • 作用:处理 props 对象,为 props 对象的每个属性设置响应式,并将其代理到 vm 实例上
function initProps (vm: Component, propsOptions: Object) {
  //存放父组件传入子组件的props
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  // 存放porps的keys
  const keys = vm.$options._propKeys = []
  // 是否是是根
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    // 获取每个popps[key]的对应值
    const value = validateProp(key, propsOptions, propsData, vm) 
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      // 为 props 的每个 key 是设置数据响应式
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      // 将props中的每个属性设为响应式
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      // 代理 key 到 vm 对象上
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}	

3.3.3 initMethods()

  • 路径src\core\instance\state.js
  • 作用
    • 校验 methoss[key]必须是一个函数
    • 查重,props和自身方法对比
    •     遍历$options的传入methods,把每一个method绑定到当前vue实例上,并将method的this指向当前vue实例
      
function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    // 判断类型
    if (process.env.NODE_ENV !== 'production') {
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      //查重
      // 和propskey对比
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      // 和自身方法对比
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    // noop空函数,绑定函数且改变this指向
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

3.3.4 initData(vm)

  • 路径src\core\instance\state.js
  • 作用
    • 获取data
    • 查重
    • 代理 data 对象上的属性到 vm 实例
    • 为 data 对象的上数据设置响应式

function initData (vm: Component) {
  let data = vm.$options.data
  // 这里是处理组件中data,如果在组件中data必须以函数的注入,如果式实例上data就是本身
  // 空值就初始化一个对象
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  // 获取data中的所有属性,获取prosps和methods
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 判断属性名称和props和methods的是否有重名
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // 把data中的成员注入到vue实例中
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 响应式处理, true式说明式根数据
  observe(data, true /* asRootData */)
}

3.3.5 observe()

  • 地址:src\core\observer\index.js
  • 作用:为对象创建观察者实例,如果对象已经被观察过,则返回已有的观察者实例,否则创建新的观察者实例
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 判断Value是否式式对象或是否是Vnode,是的话就不需要处理 
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  // ob就是Obsever的实例
  let ob: Observer | void
  // 如果value中存在_ob_(observer对象)这个属性,就直接赋值给ob返回
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 创建一个Observer对象
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

3.3.6 Observer [观察者类]

  • 位置:src\core\observer\index.js

  • 作用:

    • 定义观察者类
    • 定义_ob_属性,吧Observer对象记录下来,并且设置不可枚举
    • 给对象中每个属性设置getter和setter
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    // 观察对象
    this.value = value
    // 依赖对象
    this.dep = new Dep()
    // 实例计数器
    this.vmCount = 0
    // def.Object.defineProperty经行封装,设置属性不可枚举不可枚举
    // 在 value 对象上设置 __ob__ 属性
    def(value, '__ob__', this)
    // 数组响应式处理(后面分析)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 为数组中每个对象创建一个observer实例
      this.observeArray(value)
    } else {
      // 遍历每一个对象,装换成getter和setter
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    // 获取对象的每一个属性
    const keys = Object.keys(obj)
    // 遍历每一个属性,设置为响应式数据
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  // 遍历每一个属性,设置为响应式数据
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

3.3.7defineReactive()

  • 位置:src\core\observer\index.js
  • 作用:
    • 收集依赖,发送通知
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean // 当shallow为true的时候值监听第一层属性,false时为深度监听
) {
  // 创建调度中心,用来收集所有依赖Watcher
  const dep = new Dep()
  // 获取obj的属性描述符,如果时不可以配置的就直接返回,应为后面会重写
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 提供预定义的存取器函数
  // 记录 getter 和 setter,获取 val 值
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 判断是否需要递归观察子对象,并将子对象的属性转换成getter/setter,返回子观察对象
  let childOb = !shallow && observe(val)
  // 响应式核心
  Object.defineProperty(obj, key, {
    enumerable: true, // 可枚举
    configurable: true, // 可配置
    get: function reactiveGetter () {
      // 如果预定义的getter存在则value等于getter调用的返回值
      // 否则赋予属性值·
      const value = getter ? getter.call(obj) : val
      // 这里主要经行依赖收集,把依赖该属性的watcher对象添加搭配dep.sub数组中
      // 当数据发生变化的时候通知所有watcher
      // Dep.target实在Watcher内收集依赖的
      if (Dep.target) {
        dep.depend()
        // childOb 表示对象中嵌套对象的观察者对象,如果存在也对其进行依赖收集
        if (childOb) {
          // 如果子观察目标存在,建立子对象的依赖关系
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      // 当新值等于就职,或者新旧值为NaN的时候直接返回,注意的式js中NaN != NaN
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // 如果没有setter的时候直接返回,否则更新值
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 如果新值也是个对象观察子对象并返回,子obsever对象
      childOb = !shallow && observe(newVal)
      // 派发更新
      dep.notify()
    }
  })
}

3.3.8Dep [调度中心类]

  • 位置:src\core\observer\dep.js
  • 作用:
    • 依赖管理,收集依赖,发送通知
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

// 添加
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

// 调用watcher中的addDep
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
// Dep.target 用来存放目前正在使用的Watcher
// 全局唯一,并且一次也只能有一个watcher被使用
Dep.target = null
const targetStack = []

// 这里要注意的的是入栈并将当前的watcher赋值给Dep.target
// 每个组件会创建一个wathce对象
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

// 出栈
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

3.3.9Wather[观察者类]

  • 位置:src\core\observer\watcher.js
  • 作用:
    • 依赖管理,收集依赖,发送通知
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

4.异步更新

4.1 数组响应式原理调试

  • 调试响应式数据执行过程
    • 数据响应式处理的核心过程和数组收集依赖的过程
      • 注意数组中的属性并不是响应式
    • 当属组的数据改变的时候watcher的执行过程
  • 准备工作
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>observe</title>
</head>
<body>
    <div id="app">
        {{arr}}
    </div>
    <script src="../../dist/vue.js"></script>
    <script>
        const vm = new Vue({
        el:'#app',
        data: {
           arr:[2,3,5]
        }
        })
    </script>

</body>
</html>
  • 设置断点

oberserve1.png

  • 首先创建响应式数据,传入的data是一个对象会进入Walk方法
    • walk方法会遍历对象中的每个属性并执行defineReactive()
    • defineReactive()内,值得注意的是初次传入的data是一个对象,所以会递归监听

obsever类.png

definereacted.png

  • Observe类中会判断入参data是不是数组,如果是数组的化会重写数组的方法
    • 执行完后还会执行observeArray()递归监听

数组重写.png

  • 下一步会进入到defineProperty
    • 会对每一个属性创建一个dep,为这个属性收集依赖
    • 接着调用dep.depend(),会把这个dep对象添加到subs数组中
    • 注意的是这里会收集两次依赖,
      • 第一个是为了当前属性收集依赖
      • 对数组对象收集依赖,当数组对象发生变化的时候会通知Watcher
    • 如果当前value是数组的化会进入dependArray()

dep.png

  • dependArray() 会对数组内的元素处理
    • 数组中元素是对象被这回执行收集依赖
    • 数组中元素是数组就对递归调用
    • 到这个数组收集依赖的过程就完成了

dependArray.png

  • 查看数组数据发生改变时的执行过程
    • 设置断点再Dep类的notify()
    • 再控制到Console中改变数据,vm.arr.push(10)

监听notify.png

  • 回车后进入sub.notify()
    • 收件会拷贝一根subs,防止后续再操作中会更新这个数组
    • 遍历subs数组中的watcher对象,调用Watcher.update()

dep.notify.png

  • 进入到queueWatcher方法内

    • 首先会记录这个Watcher的id,然后去判断id是都,再队列象内,没有的化证明数组未被处理过
    • 然后判断是否再刷新队列,不是的化就把这个watcher对象压入到queue队列中
    • 然后再nextTick把刷新队列的flushSecheduleQueue()传入

queueWatcher.png

  • 在flushSecheduleQueue方法中

    • 在方法中首先会对Wacher对象进行排序
    • 遍历队列
    • 触发beforeUpdata()钩子函数
    • 执行Wacher.run()
    • 在run方法内最终回到vm._update(vm._render(), hydrating)去更新视图
    • 后续就执行有些清除和reset工作

flushSchedulerQueue.png

4.2源码解读

4.2.1 dep.notify

  • 路径/src/core/observer/watcher.js
  • 作用
    • 通知 dep 中的所有 watcher,执行 watcher.update() 方法
notify () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  // 遍历 dep 中存储的 watcher,执行 watcher.update()
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

4.2.1 wather.update

  • 路径src\core\observer\watcher.js
  • 作用
    • 通知 dep 中的所有 watcher,执行 watcher.update() 方法
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      // 懒执行时会走导致,比如computed
      // 将dirty设置为true,在组件更行之后,当响应式数据在此被更新的时候,执行computed Getter
      // 重新执行computed回调函数,计算啊下新值,然后换缓存到 Watcer.value
      this.dirty = true
    } else if (this.sync) {
      // 当同步执行会走这
      // 比如this.$watch()
      this.run()
    } else {
      // 当时渲染Wathcer的时候会进入到这里来
      queueWatcher(this)
    }
  }

4.2.3 queueWatcher()

  • 位置src\core\observer\scheduler.js
  • 作用:
    • 将 watcher 放入 watcher 队列
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  // 如果 watcher 已经存在,则跳过,不会重复入队
  if (has[id] == null) {
    // 缓存 watcher.id,用于判断 watcher 是否已经入队
    has[id] = true
    if (!flushing) {
      // 当前没有处于刷新队列状态,watcher 直接入队
      queue.push(watcher)
    } else {
      // 已经在刷新队列了
      // 从队列末尾开始倒序遍历,根据当前 watcher.id 找到它大于的 watcher.id 的位置,然后将自己插入到该位置之后的下一个位置
      // 即将当前 watcher 放入已排序的队列中,且队列仍是有序的
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        // 直接刷新调度队列
        flushSchedulerQueue()
        return
      }
      /**
       * 熟悉的 nextTick => vm.$nextTick、Vue.nextTick
       *   1、将 回调函数(flushSchedulerQueue) 放入 callbacks 数组
       *   2、通过 pending 控制向浏览器任务队列中添加 flushCallbacks 函数
       */
      nextTick(flushSchedulerQueue)
    }
  }
}

4.2.4 nextTick

  • 位置/src/core/util/next-tick.js
  • 作用:
    • 用 try catch 包装 flushSchedulerQueue 函数,然后将其放入 callbacks 数组
    • 调用timerFunc()将异步任务压入到浏览器的异步任务队列中
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 将回调函数用trycatch 包装一层,方便一场捕获
  // 然后将 包装后的函数 放到这个callback数组
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // pedding是一flag 确保却列中异步任务队列中只有一个flushCallbacks函数
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

4.2.5 timerFunc

  • 位置/src/core/util/next-tick.js
  • 作用:
    • 将 flushCallbacks 函数放入浏览器的异步任务队列中,根据浏览器的支持优雅降级
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  // 首选 Promise.resolve().then()
  timerFunc = () => {
    // 在 微任务队列 中放入 flushCallbacks 函数
    p.then(flushCallbacks)
    /**
     * 在有问题的UIWebViews中,Promise.then不会完全中断,但是它可能会陷入怪异的状态,
     * 在这种状态下,回调被推入微任务队列,但队列没有被刷新,直到浏览器需要执行其他工作,例如处理一个计时器。
     * 因此,我们可以通过添加空计时器来“强制”刷新微任务队列。
     */
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // MutationObserver 次之
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 再就是 setImmediate,它其实已经是一个宏任务了,但仍然比 setTimeout 要好
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // 最后没办法,则使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

4.2.6 flushCallbacks

  • 位置/src/core/util/next-tick.js
  • 作用:
    • 将Pending再次职位false,表示下一个flushCallbacks函数可以进入浏览的异步任务队列
    • 清空callback数组
    • 执行callback数组中的函数
      • flushSchedulerQueue中的传入函数
      • 用户自己调用的this.$nextTick传递回调函数
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

4.2.7 flushSchedulerQueue()

  • 位置src\core\observer\scheduler.js
  • 作用:
    • 更新 flushing 为 ture,表示正在刷新队列,在此期间往队列中 push 新的 watcher 时需要特殊处理(将其放在队列的合适位置)
    • 按照队列中的 watcher.id 从小到大排序,保证先创建的 watcher 先执行,也配合 第一步
    • 遍历 watcher 队列,依次执行 watcher.before、watcher.run
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // ==> 组件的更新顺序是由父组件到子组件,因为我们的创建顺序就是由父到子
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // ==> 组件的用户Watcher要在渲染Watcher之前执行
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  // ==> 如果一个组件在他执行他的父组件前被销毁了那这个Watcher应该被跳过
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  // 这里直接使用了 queue.length,动态计算队列的长度,没有缓存长度,是因为在执行现有 watcher 期间队列中可能会被 push 进新的 watcher
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
    // 渲染Watcher去触发beforeDeta的钩子
      watcher.before()
    }
    // 清除ID,下次可以被调用
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }

4.2.8 wacher.run

  • 位置/src/core/observer/watcher.js
  • 作用:
    • 由 刷新队列函数 flushSchedulerQueue 调用,如果是同步 watch,则由 this.update 直接调用,
    • 执行实例化 watcher 传递的第二个参数,updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数)
    • 更新旧值为新值
    • 执行实例化 watcher 时传递的第三个参数,比如用户 watcher 的回调函数

run () {
  if (this.active) {
    // 调用 this.get 方法
    const value = this.get()
    if (
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // 更新旧值为新值
      const oldValue = this.value
      this.value = value

      if (this.user) {
        // 如果是用户 watcher,则执行用户传递的第三个参数 —— 回调函数,参数为 val 和 oldVal
        try {
          this.cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "${this.expression}"`)
        }
      } else {
        // 渲染 watcher,this.cb = noop,一个空函数
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}

4.2.8 wacher.get

  • 位置/src/core/observer/watcher.js
  • 作用:
    • 执行 this.getter,并重新收集依赖
      • 因为触发更新说明有响应式数据被更新了,但是被更新的数据虽然已经经过 observe 观察了,但是却没有进行依赖收集
      • 所以,在更新页面时,会重新执行一次 render 函数,执行期间会触发读取操作,这时候进行依赖收集
    • this.getter 是实例化 watcher 时传递的第二个参数,一个函数或者字符串,比如:updateComponent 或者 parsePath 返回的函数
get () {
    pushTarget(this)
    // value 为回调函数执行的结果
    let value
    const vm = this.vm
    try {
      // 执行回调函数,比如 updateComponent,进入 patch 阶段
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

5.全局API

5.1源码解读

5.1.1 global-api(入口文件)

  • 参考2.4.6

5.1.2 Vue.util

  • 向外暴露一些内置方法
  // 这些工具方法不视为全局Api的一部分,不用然会有风险
  Vue.util = {
    // 日志方法
    warn,
    // 将A对象的属性复制到B对象上
    extend,
    // 合并选项
    mergeOptions,
    // 给对象设置getter、setter,涉及到依赖收集,更新触发依赖方法
    defineReactive
  }
  • extend
/**
 * Mix properties into target object.
 */
export function extend (to: Object, _from: ?Object): Object {
  for (const key in _from) {
    to[key] = _from[key]
  }
  return to
}
  • mergeOptions, defineReactive参考之气那内容

5.1.3 Vue.set

  • 位置\src\core\observer\index.js

  • 作用:

    • 给对象或属性设置响应式数据
  export function set (target: Array<any> | Object, key: any, val: any): any {
    if (process.env.NODE_ENV !== 'production' &&
      (isUndef(target) || isPrimitive(target))
    ) {
      warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
    }
    // 处理数组,Vue.set(arr,idx,val)
    if (Array.isArray(target) && isValidArrayIndex(key)) {
      target.length = Math.max(target.length, key)
      // 利用splice去实现
      target.splice(key, 1, val)
      return val
    }
    // 处理对象的情况
    // 如果属性原来存在,就直接更改值
    if (key in target && !(key in Object.prototype)) {
      target[key] = val
      return val
    }
    const ob = (target: any).__ob__
    if (target._isVue || (ob && ob.vmCount)) {
      process.env.NODE_ENV !== 'production' && warn(
        'Avoid adding reactive properties to a Vue instance or its root $data ' +
        'at runtime - declare it upfront in the data option.'
      )
      return val
    }
    if (!ob) {
      target[key] = val
      return val
    }
    // 给新属性设置getter 和 setter 并设置成响应式数据
    defineReactive(ob.value, key, val)
    // 依赖通知更新
    ob.dep.notify()
    return val
  }

5.1.4 Vue.delete

  • 位置\src\core\observer\index.js

  • 作用:

    • 删除一个属性并触发响应式
export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 处理数组情况
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  // 处理对象
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

5.1.5 Vue.nextTick

  • 参考上文4.2.4

5.1.6 Vue.use

  • 位置src\core\global-api\use.js
  • 作用 执行插件中暴露出来的install方法
/* @flow */

import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
    // 防止重复注册
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    // 把参数转成数组并且把第一个参数去除
    const args = toArray(arguments, 1)
    // 把this(vue)插入到第一个元素中
    // 这里就是如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,
    // 它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    // 把插件存储到数组中
    installedPlugins.push(plugin)
    return this
  }
}

5.1.7 Vue.mixin

  • 位置:src\core\global-api\mixin.js
  • 作用:合并两个option
import { mergeOptions } from '../util/index'
// 实际就是利用mergeOptions方法
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

5.1.8Vue.extend

  • 位置:src\core\global-api\extend.js
  • 作用:扩展Vue子类。预设一些配置项
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    // 创建一个缓存,当重复使用的时候会直接调用缓存
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    // 验证组件名称
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    // 定义一个Vue的子类
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // 设置子类的原型对象
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    // 合并传入的选项和基础类的选项
    // 可以通过Vue.extend 方法定义一个子类,预设一些配置象,当使用Vue构造数的时候可以使用
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // 将props 和computed代理到子类上,在子类通过this.XX的方式访问
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // 组件实现递归自调用的实现原理
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}