Vue.js 源码剖析-响应式原理 学习的心得体会+学习笔记(青铜到铂金)

Vue.js 源码剖析-响应式原理 学习的心得体会+学习笔记

心得体会

不知不觉在拉钩教育大前端高薪训练营已经学习2个多月了,在这两个多月的学习中,自己收获了很多,不仅养成了每次学习时都保证高质量的记好学习笔记的习惯,而且对自己整个的前端基础知识进行了巩固,对基础知识有了更深的理解,勉强做到了知其然,更知其所以然。

接下来我想谈下拉钩教育大前端高薪训练营的课程体系:这门课的内容非常的好,为什么这么说呢?原因有以下几点:

  • 1.课程的深度做的足够好:课程不仅会教同学们怎么使用前端经常使用的框架(vue、react 和 anguler),而且更会教为什么是这样用这些框架。最令人惊喜的是老师会配合 xmind(框架的执行栈的流程图) 和浏览器的断点调试(js在浏览器中的执行过程和渲染过程整个的走一遍)来加深对源码的理解和记忆,并激发我们新的问题和思考。还有像 promise A+ 的代码会带着我们理清整个思路,然后自己实现一遍;webpack 核心内容的讲解以及 怎样手写一个自己的 loader 和 plugin 等等,这里只是做举例说明,太多了就不一一赘述了。
  • 2.课程的广度也非常的好:
    • 1)会讲到 JS 的基础知识并对这些基础知识进行加深和巩固;
    • 2)其次会讲到前端的三大框架(vue、react 和 angular),这三个框架不是泛泛而谈,是讲的很深的那种;
    • 3)还会讲到前端工程化的东西,分别从脚手架工具、自动化构建、模块化开发、规范化标准四个维度介绍前端工程化具体该如何落地、如何实践,以此应对复杂前端应用的开发和维护过程,提高开发者的工作效率,降低项目维护成本,从而更好地适应大前端时代下的前端开发工作;
    • 4)Node.js全栈开发:带你打通前后端边界,掌握前端开发的新舞台:Node.js,通过 Node.js 平台及其相关技术完成应用程序的服务端开发,其中也会涉及目前主流的一些标准和框架,例如 GraphQL、Egg.js,掌握这些后,你可以完成整个网站或 App 的前后端开发,跻身成为全栈型的前端人才。
    • 5)以技术专题的形式学习很多具有趋势性的技术,例如:Serverless、PWA、微前端、数据可视化等等。通过这些技术专题的学习把握技术走势,同时也为你的技能增添闪光点。
    • 6)前端常用的算法
  • 3.讲师的质量也非常的高,讲师的对课程的要求也非常的高,目前讲师经验都非常丰富,汪老师、教瘦、鸠摩智、Storm 目前都是资深技术前端专家。
    • 1)汪磊曾任某知名电商团队技术总监,还是第一届 HTML5 中国产业联盟特邀嘉宾,最早一代小程序开发人员,小程序内测第二天就研发了国内首套小程序教程
    • 2)鸠摩智 曾任职于中国网库、北京因脉科技等公司,担任项目经理;
    • 3)教瘦 十来年前后端开发经验,精通各类客户端开发技术

除此之外,我们还有两位助教老师24小时在群内答疑,有疑惑的地方,就可以立马去群里问助教老师,老师会看到问题就会立马回复,如果是代码执行问题,还会把你的代码下载下来亲自运行排查,真的是助教中的良心。班主任小雪美女也会在群里每天督促同学们交作业,遇到作业提交问题、听课问题都可以直接找班主任老师(PS:小雪美女还会经常发些鸡汤,我都快喝醉了,哈哈哈哈哈哈哈)。

在拉钩教育大前端课程中,只要你能坚持下来(对自己的期待吧,希望自己以后无论做任何事,都能坚持到最后)你就会遇见更好的自己。

任务一:Vue.js 源码剖析-响应式原理。xmind文件见:https://github.com/smallSix6/fed-e-task-liuhuijun/tree/master/fed-e-task-03-02

1、Vue 的不同构建版本

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jlUiYQW3-1596333240773)(…/images/vueAllJs.png)]
  • Full:包含编译器和运行时的构建。
  • 编译器:负责将模板字符串编译为JavaScript渲染函数的代码。
  • 运行时:负责创建Vue实例,渲染和修补虚拟DOM等的代码。基本上,所有内容都减去编译器。
  • UMD:UMD构建可通过script标签直接在浏览器中使用。来自 https://unpkg.com/vue 的 Unpkg CDN的默认文件是Runtime + Compiler UMD构建(vue.js)。
  • CommonJS的:CommonJS的建立旨在用于与旧捆扎机像 browserify或的WebPack 1。这些捆绑器(pkg.main)的默认文件是“仅运行时” CommonJS构建(vue.runtime.common.js)。
  • ES模块:ES模块版本旨在与现代捆绑器(例如 webpack 2或汇总)一起使用。这些捆绑程序(pkg.module)的默认文件是“仅运行时ES模块”构建(vue.runtime.esm.js)。

2、入口文件

  • 寻找入口文件
    • 查看 dist/vue.js 的构建过程
  • 执行构建
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
  • script/config.js 的执行过程
    • 作用:生成 rollup 构建的配置文件
    • 使用环境变量 TARGET=web-full-de
    // 判断环境变量是否有 TARGET
    // 如果有的话,使用 gitConfig() 生成 rollup 配置文件
    if (process.env.TARGET) {
        module.exports = genConfig(process.env.TARGET)
    } else {
        exports.getBuild = genConfig
        exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
    }
    
  • 最后生成 src/platforms/web/entry-runtime-with-compiler.js 文件
  • 通过查看源码解决下面问题
    • 观察以下代码,通过阅读源码,回答在页面上输出的结果
    const vm = new Vue({
      el: '#app'
      template: '<h3> Hello Template </h3>,
      render (h) {
        return h('h4', 'Hello Render')
      }
    })
    
  • 阅读源码记录
    • el 不能是 body 或者 html 标签
    • 如果没有 render, 把 template 转换成 render 函数
    • 如果有 render 方法,直接调用 mount 挂载 DOM
Vue.prototype.$mount = function(
    el ? : string | Element,
    // 非 ssr 情况下为 false, ssr 时候为 true
    hydrating ? : boolean
): Component {
    // 获取 el 对象
    el = el && query(el)

    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
        process.env.NODE_ENV !== 'production' && warn(
            `Do not mount Vue to  or  - mount to normal elements instead.`
        )
        return this
    }

    const options = this.$options
        // resolve template/el and convert to render function
    if (!options.render) {
       ...
    }
    // 调用 mount 方法,渲染 DOM
    return mount.call(this, el, hydrating)
}
  • 页面渲染流程的 Call Stack 为:
    • Vue
    • Vue._init
    • Vue.$mount

3、Vue 初始化的过程

  • 四个导出 Vue 的模块
    • src/platforms/web/entry-runtime-with-compiler.js
      • web 平台相关的入口
      • 重写了平台相关的 $mount() 方法
      • 注册了 Vue.compile() 方法,传递一个 HTML 字符串返回 render 函数
    • src/platforms/web/runtime/index.js
      • web 平台相关
      • 注册和平台相关的全局指令:v-model、v-show
      • 注册和平台相关的全局组件:v-transition、v-transition-group
      • 全局方法:
        • patch:把虚拟 DOM 转换成真实 DOM
        • $mount: 挂载方法
    • src/core/index.js
      • 与平台无关
      • 设置了 vue 的静态方法, initGlobalAPI(Vue)
  • src/core/instance/index.js
    • 与平台无关
    • 定义了构造函数,调用了 this._init(options) 方法
    • 给 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')
        }
        // 调用 _init() 方法
        this._init(options)
    }
    
    // 注册 vm 的 _init() 方法,初始化 vm
    initMixin(Vue)
        // 注册 vm 的 $data/$props/$set/$delete/$watch
    stateMixin(Vue)
        // 初始化事件相关方法
        // $on/$once/$off/$emit
    eventsMixin(Vue)
    
    // 初始化生命周期相关的混入方法
    // _update/$forceUpdate/$destroy
    lifecycleMixin(Vue)
        // 混入 render
        // $nextTick/_render
    renderMixin(Vue)
    
    export default Vue
    

4、Vue 初始化——静态成员

  • src/core/global-api/index.js
    • 设置 Vue 的静态方法,initGlobalAPI(Vue)
     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 对象
         // 在 src/platforms/web/runtime/index.js 里设置了 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.
         Vue.util = {
             warn,
             extend,
             mergeOptions,
             defineReactive
         }
    
         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 = 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(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)
     }
    
    • initUse(Vue) 注册 Vue.use(),用来注册插件
    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
        // 把数组中的第一个元素(plugin)去除
        const args = toArray(arguments, 1)
        // 把 this(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、Vue 初始化——实例成员

  • src/core/instance/index.js
// 此处不用 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')
    }
    // 调用 _init() 方法
    this._init(options)
}

// 注册 vm 的 _init() 方法,初始化 vm
initMixin(Vue)
    // 注册 vm 的 $data/$props/$set/$delete/$watch
stateMixin(Vue)
    // 初始化事件相关方法
    // $on/$once/$off/$emit
eventsMixin(Vue)

// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
    // 混入 render
    // $nextTick/_render
renderMixin(Vue)

export default Vue

6、Vue 初始化——实例成员——init

  • initMixin(Vue) 注册 vm 的 _init() 方法,初始化 vm
export function initMixin(Vue: Class < Component > ) {
    // 给 Vue 实力增加 init() 方法
    // 合并 options 并且初始化操作
    Vue.prototype._init = function(options ? : Object) {
        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
        vm._isVue = true
            // merge 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
            // vm 的生命周期相关变量初始化
            // $children/$parent/$root/$refs
        initLifecycle(vm)
            // vm 的事件监听初始化,父组件绑定在当前组件上的事件
        initEvents(vm)
            // vm 的编译 render 初始化
            // $slot/$scopedSlots/_c/$createElement/$attrs/$listenrs
        initRender(vm)
            // beforeCreate 生命钩子的回调
        callHook(vm, 'beforeCreate')
            // 把 inject 的成员注入到 vm 上
        initInjections(vm) // resolve injections before data/props
            // 初始化 vm 的 _props/methods/_data/computed/watch
        initState(vm)
            // 初始化 provide
        initProvide(vm) // resolve provide after data/props
            // created 生命钩子的回调
        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)
        }
    }
}
  • initState(vm) 初始化 vm 的 _props/methods/_data/computed/watch
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)
  }
}

7、首次渲染过程——Vue.prototype._init

  • 首次渲染主要流程如下:

    • 1、初始化实例成员:
      • initMixin(Vue)
      • stateMixin(Vue)
      • eventsMixin(Vue)
      • lifecycleMixin(Vue)
      • renderMixin(Vue)
    • 2、初始化静态成员:
      • initGlobalAPI(Vue)
    • 3、调用 _init() 方法:this._init(options) 即 Vue.prototype._init
      • Vue.prototype._init 源码文件见 src/core/instance/init.js
      export function initMixin(Vue: Class < Component > ) {
          // 给 Vue 实力增加 init() 方法
          // 合并 options 并且初始化操作
          Vue.prototype._init = function(options ? : Object) {
              ...
              // 如果是 Vue 实例不需要被 observe
              vm._isVue = true
                  // merge options
              if (options && options._isComponent) {
                  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
                  // vm 的生命周期相关变量初始化
                  // $children/$parent/$root/$refs
              initLifecycle(vm)
                  // vm 的事件监听初始化,父组件绑定在当前组件上的事件
              initEvents(vm)
                  // vm 的编译 render 初始化
                  // $slot/$scopedSlots/_c/$createElement/$attrs/$listenrs
              initRender(vm)
                  // beforeCreate 生命钩子的回调
              callHook(vm, 'beforeCreate')
                  // 把 inject 的成员注入到 vm 上
              initInjections(vm) // resolve injections before data/props
                  // 初始化 vm 的 _props/methods/_data/computed/watch
              initState(vm)
                  // 初始化 provide
              initProvide(vm) // resolve provide after data/props
                  // created 生命钩子的回调
              callHook(vm, 'created')
              ...
              if (vm.$options.el) {
                  // 调用 mount 方法(重点)
                  vm.$mount(vm.$options.el)
              }
          }
      }
      
      • vm.$mount (重写 $mount 方法的主要作用就是 compiler template) 方法源码如下,文件见 src/platform/web/entry-runtime-with-compiler.js :
      const mount = Vue.prototype.$mount
      Vue.prototype.$mount = function(
          el ? : string | Element,
          // 非 ssr 情况下为 false, ssr 时候为 true
          hydrating ? : boolean
      ): Component {
          // 获取 el 对象
          el = el && query(el)
          ...
          const options = this.$options
              // resolve template/el and convert to render function
          if (!options.render) {
              let template = options.template
              if (template) {
                  if (typeof template === 'string') {
                      if (template.charAt(0) === '#') {
                          template = idToTemplate(template)
                              /* 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) {
                      template = template.innerHTML
                  } else {
                      if (process.env.NODE_ENV !== 'production') {
                          warn('invalid template option:' + template, this)
                      }
                      return this
                  }
              } else if (el) {
                  template = getOuterHTML(el)
              }
              if (template) {
                  ...
                  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
                  ...
              }
          }
          // 调用 mount 方法,渲染 DOM
          return mount.call(this, el, hydrating)
      }
      
      • Vue.prototype.$mount(这个方法在 runtime-with-compiler 的时候会被重写) 方法源码如下,文件见 src/platforms/web/runtime/index.js:
      Vue.prototype.$mount = function(
          el ? : string | Element,
          hydrating ? : boolean
      ): Component {
          el = el && inBrowser ? query(el) : undefined
          return mountComponent(this, el, hydrating)
      }
      
      • mountComponent 方法源码如下,文件见 src/core/instance/lifecycle.js:
      export function mountComponent (
        vm: Component,
        el: ?Element,
        hydrating?: boolean
      ): Component {
        vm.$el = el
        if (!vm.$options.render) {
          vm.$options.render = createEmptyVNode
        }
        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 = () => {
            vm._update(vm._render(), hydrating)
          }
        }
        new Watcher(vm, updateComponent, noop, {
          before () {
            if (vm._isMounted && !vm._isDestroyed) {
              callHook(vm, 'beforeUpdate')
            }
          }
        }, true /* isRenderWatcher */)
        hydrating = false
        if (vm.$vnode == null) {
          vm._isMounted = true
          callHook(vm, 'mounted')
        }
        return vm
      }
      
  • 首次渲染的流程图见:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PlZHUct4-1596333240775)(…/images/首次渲染过程.png)]

8、数据响应式原理——响应式处理入口

  • 通过查看源码解决下面问题
    • vm.msg = { count: 0 }, 重新给属性赋值,是否是响应式的?
    • vm.arr[0] = 4,给数组元素赋值,试图是否会更新
    • vm.arr.length = 0,修改数组的 length,试图是否会更新
    • vm.arr.push(4),试图是否会更新
  • 响应式处理的入口
    • 整个响应式处理的过程是比较复杂的,下面我们先从入口开始看起
      • src/core/instance/init.js
        • initState(vm) vm状态的初始化
        • 初始化了 _data、_props、methods 等
      • initData(vm): src/core/instance/state.js
      // 数据的初始化
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data={}, true  /*  asRootData */)
      }
      
      • observe(value: any, asRootData: ? boolean): src/core/observer/index.js
      export function observe(value: any, asRootData: ? boolean): Observer | void {
          // 判断 value 是否是对象
          if (!isObject(value) || value instanceof VNode) {
              return
          }
          let ob: Observer | void
              // 如果 value 有 __ob__(observer 对象) 属性  结束
          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
      }
      
      • class Observer: src/core/observer/index.js
      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()
                  // 初始化实例的 vmCount 为0
              this.vmCount = 0
                  // 将实例挂载到观察对象的 __ob__ 属性
              def(value, '__ob__', this)
                  // 数组的响应式处理
              if (Array.isArray(value)) {
                  if (hasProto) {
                      protoAugment(value, arrayMethods)
                  } else {
                      copyAugment(value, arrayMethods, arrayKeys)
                  }
                  // 为数组中的每一个对象创建一个 observer 实例
                  this.observeArray(value)
              } else {
                  // 遍历对象中的每一个属性,转换成 setter/getter
                  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])
              }
          }
      }
      
      • defineReactive (为一个对象定义一个响应式的属性): src/core/observer/index.js
      export function defineReactive(
          obj: Object,
          key: string,
          val: any,
          customSetter ? : ? Function,
          shallow ? : boolean
      ) {
          // 创建依赖对象实例
          const dep = new Dep()
              // 获取 obj 的属性描述符对象
          const property = Object.getOwnPropertyDescriptor(obj, key)
          if (property && property.configurable === false) {
              return
          }
          // 提供预定义的存取器函数
          // cater for pre-defined getter/setters
          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 对象,则建立依赖
                  if (Dep.target) {
                      dep.depend()
                          // 如果子观察目标存在,建立子对象的依赖关系
                      if (childOb) {
                          childOb.dep.depend()
                              // 如果属性是数组,则特殊处理收集数组对象依赖
                          if (Array.isArray(value)) {
                              dependArray(value)
                          }
                      }
                  }
                  // 返回属性值
                  return value
              },
              set: function reactiveSetter(newVal) {
                  // 如果预定义的 getter 存在则 value 等于 getter 调用的返回值
                  // 否则直接赋予属性值
                  const value = getter ? getter.call(obj) : val
                      // 如果新值等于旧值或者新值旧值为 NaN 则不执行
                      /* eslint-disable no-self-compare */
                  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
                      // 如果预定义 setter 存在则调用,否则直接更新新值
                  if (setter) {
                      setter.call(obj, newVal)
                  } else {
                      val = newVal
                  }
                  // 如果新值是对象,观察子对象并返回子对象的 observer 对象
                  childOb = !shallow && observe(newVal)
                      // 派发更新(发布更改通知)
                  dep.notify()
              }
          })
      }
      
  • 数据响应式原理——依赖收集
    • defineReactive:入口在 src/core/observer/index.js/defineReactive 函数里
    // 创建依赖对象实例
    const dep = new Dep()
    if (Dep.target) {
      dep.depend()
          // 如果子观察目标存在,建立子对象的依赖关系
      if (childOb) {
          childOb.dep.depend()
              // 如果属性是数组,则特殊处理收集数组对象依赖
          if (Array.isArray(value)) {
              dependArray(value)
          }
      }
    }
    
    • class Dep(创建依赖对象实例):文件在 src/core/observer/dep.js
    export default class Dep {
        // 静态属性,watcher 对象
        static target: ? Watcher;
        // dep 实例 Id
        id: number;
        // dep 实例对应的 watcher 对象/订阅者数组
        subs: Array < Watcher > ;
    
        constructor() {
                this.id = uid++
                    this.subs = []
            }
            // 添加新的订阅者 watcher 对象
        addSub(sub: Watcher) {
                this.subs.push(sub)
            }
            // 移除订阅者
        removeSub(sub: Watcher) {
                remove(this.subs, sub)
            }
            // 将观察对象和 watcher 建立依赖
        depend() {
                if (Dep.target) {
                    // 如果 target 存在,把 dep 对象添加到 watcher 的依赖中
                    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()
            }
        }
    }
    
    • Dep.target 的 target 的添加逻辑在组件 mount 阶段时 mountComponent 函数里会添加 new Watcher() ,Watcher 类里的 constructor 里会调用 get 方法中的 pushTarget(this)
      • class Watcher: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) {
                try {
                  this.cb.call(this.vm, value, oldValue)
                } catch (e) {
                  handleError(e, this.vm, `callback for watcher "${this.expression}"`)
                }
              } 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
          }
        }
      }
      
      • pushTarget(将 watcher 挂载到 Dep 的 target 属性下):src/core/observer/dep.js
      // Dep.target 用来存放目前正在使用的 watcher
      // 全局唯一,并且只能有一个 watcher 被使用
      Dep.target = null
      const targetStack = []
      // 入栈并将当前 watcher 赋值给 Dep.target
      export function pushTarget(target: ? Watcher) {
          targetStack.push(target)
          Dep.target = target
      }
      
      • dep.depend(收集依赖的过程):src/core/observer/index.js/defineReactive 函数里
      • Dep 类里的 depend 方法源码如下:src/core/observer/dep.js
              // 将观察对象和 watcher 建立依赖
          depend() {
              // Dep.target = watcher
              if (Dep.target) {
                  // 如果 target 存在,把 dep 对象添加到 watcher 的依赖中
                  Dep.target.addDep(this)
              }
          }
      
      • Watcher 类里的 addDep 方法源码如下:src/core/observer/watcher.js
      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)
          }
        }
      }
      
      • Dep 类里的 addSub 方法源码如下:src/core/observer/dep.js
      // 添加新的订阅者 watcher 对象
      addSub(sub: Watcher) {
        this.subs.push(sub)
      }
      
  • 依赖收集的流程图见:https://github.com/smallSix6/fed-e-task-liuhuijun/blob/master/fed-e-task-03-02/note/依赖收集流程.xmind

9、数据响应式原理——数组

  • 数组响应式的入口 class Observer:src/core/observer/index.js
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()
            // 初始化实例的 vmCount 为0
        this.vmCount = 0
            // 将实例挂载到观察对象的 __ob__ 属性
        def(value, '__ob__', this)
            // 数组的响应式处理
        if (Array.isArray(value)) {
            if (hasProto) {
                protoAugment(value, arrayMethods)
            } else {
                copyAugment(value, arrayMethods, arrayKeys)
            }
            // 为数组中的每一个对象创建一个 observer 实例
            this.observeArray(value)
        } else {
            // 遍历对象中的每一个属性,转换成 setter/getter
            this.walk(value)
        }
    }
  ...
}
  • Watcher类
    • Watcher 分为三种,Computed Watcher、用户 Watcher(侦听器)、渲染 Watcher
    • 渲染 Watcher 的创建时机
      • mountComponent:src/core/instance/lifecycle.js
      export function mountComponent (
        vm: Component,
        el: ?Element,
        hydrating?: boolean
      ): Component {
        vm.$el = el
        ...
        let updateComponent
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          ...
        } else {
          updateComponent = () => {
            vm._update(vm._render(), hydrating)
          }
        }
        new Watcher(vm, updateComponent, noop, {
          before () {
            if (vm._isMounted && !vm._isDestroyed) {
              callHook(vm, 'beforeUpdate')
            }
          }
        }, true /* isRenderWatcher */)
        ...
        return vm
      }
      
  • 响应式数组对象发生改变时的流程:
    • dep 的 notify():src/core/observer/dep.js
    // 发布通知
    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
            // 根据 id 来排序 sub
            subs.sort((a, b) => a.id - b.id)
        }
        // 调用每个订阅者的 update 方法实现更新
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update()
        }
    }
    
    • sub[i].update():c
    update() {
        /* istanbul ignore else */
        if (this.lazy) {
            this.dirty = true
        } else if (this.sync) {
            this.run()
        } else {
            queueWatcher(this)
        }
    }
    
    • queueWatcher(this):src/core/observer/scheduler.js
    export function queueWatcher (watcher: Watcher) {
      const id = watcher.id
      if (has[id] == null) {
        has[id] = true
        if (!flushing) {
          queue.push(watcher)
        } else {
          // if already flushing, splice the watcher based on its id
          // if already past its id, it will be run next immediately.
          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(flushSchedulerQueue)
        }
      }
    }
    
    • nextTick(flushSchedulerQueue):src/core/observer/scheduler.js
    • flushSchedulerQueue:src/core/observer/scheduler.js
    function flushSchedulerQueue () {
      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)
      // 3. If a component is destroyed during a parent component's watcher run,
      //    its watchers can be skipped.
      queue.sort((a, b) => a.id - b.id)
    
      // do not cache length because more watchers might be pushed
      // as we run existing watchers
      for (index = 0; index < queue.length; index++) {
        watcher = queue[index]
        if (watcher.before) {
          watcher.before()
        }
        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
          }
        }
      }
    
    • watcher.run():src/core/observer/watcher.js
    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) {
                    try {
                        this.cb.call(this.vm, value, oldValue)
                    } catch (e) {
                        handleError(e, this.vm, `callback for watcher "${this.expression}"`)
                    }
                } else {
                    this.cb.call(this.vm, value, oldValue)
                }
            }
        }
    }
    
    • this.get() 即 watcher 的 get 方法,触发收集依赖和更新

10、set——源码

  • 定义位置
    • Vue.set()
      • src/core/global-api/index.js
      Vue.set = set
      Vue.delete = del
      Vue.nextTick = nextTick
      
    • vm.$set()
      • src/core/instance/index.js
          // 注册 vm 的 $data/$props/$set/$delete/$watch
      stateMixin(Vue)
      
      • src/core/instance/state.js
      Vue.prototype.$set = set
      Vue.prototype.$delete = del
      
  • 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)}`)
    }
    // 判断 target 是否是对象,key 是否是合法的索引
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key)
            // 通过 splice 对 key 位置的元素进行替换
            // splice 在 array.js 进行了响应式的处理
        target.splice(key, 1, val)
        return val
    }
    // 如果 key 在对象中已经存在直接赋值
    if (key in target && !(key in Object.prototype)) {
        target[key] = val
        return val
    }
    // 获取 target 中的 observer 对象
    const ob = (target: any).__ob__
        // 如果 target 是 vue 实例或者 $data 直接返回
    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
    }
    // 如果 ob 不存在,target 不是响应式对象,则不添加响应式的处理并直接赋值
    if (!ob) {
        target[key] = val
        return val
    }
    // 把 key 设置为响应式属性
    defineReactive(ob.value, key, val)
        // 发送通知
    ob.dep.notify()
    return val
}

11、delete——源码

  • vm.$delete
    • 功能:删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制,但是你应该很少会使用它。
      • 注意:目标对象不能是一个 Vue 实例或 Vue 实例的根数据对象。
    • 示例:
    vm.$delete(vm.obj, 'msg')
    
  • 定义位置
    • Vue.delete: src/core/global-api/index.js
    • 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)}`)
        }
        // 判断是否是数组,以及 key 是否合法
        if (Array.isArray(target) && isValidArrayIndex(key)) {
            // 如果是数组通过 splice 删除
            // splice 做过响应式处理
            target.splice(key, 1)
            return
        }
        // 获取 target 的 ob 对象
        const ob = (target: any).__ob__
            // 如果 target 是 vue 实例或者 $data 直接返回
        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
        }
        // 如果 target 对象没有 key 属性直接返回
        if (!hasOwn(target, key)) {
            return
        }
        // 删除属性
        delete target[key]
        if (!ob) {
            return
        }
        // 发送通知
        ob.dep.notify()
    }
    

12、watch——回顾

  • vm.$watch(expOrFn, callback, [options])
    • 功能:观察 Vue 实例变化的一个表达式或计算属性函数,回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代

    • 参数:

      • expOrFn:要监视的 $data 中的属性,可以是表达式或函数
      • callback:数据变化后执行函数
        • 函数:回调函数
        • 对象:具有 handler 属性(字符串或者函数),如果该属性为字符串则 methods 中相应的定义
      • options:可选的选项
        • deep:布尔类型,深度监听
        • immediate:布尔类型,是否立即执行一次回调函数
    • 示例:

    var vm = new Vue({
      data: {
        a: 1,
        b: 2,
        c: 3,
        d: 4,
        e: {
          f: {
            g: 5
          }
        }
      },
      watch: {
        a: function (val, oldVal) {
          console.log('new: %s, old: %s', val, oldVal)
        },
        // 方法名
        b: 'someMethod',
        // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
        c: {
          handler: function (val, oldVal) { /* ... */ },
          deep: true
        },
        // 该回调将会在侦听开始之后被立即调用
        d: {
          handler: 'someMethod',
          immediate: true
        },
        // 你可以传入回调数组,它们会被逐一调用
        e: [
          'handle1',
          function handle2 (val, oldVal) { /* ... */ },
          {
            handler: function handle3 (val, oldVal) { /* ... */ },
            /* ... */
          }
        ],
        // watch vm.e.f's value: {g: 5}
        'e.f': function (val, oldVal) { /* ... */ }
      }
    })
    vm.a = 2 // => new: 2, old: 1
    
    • 三种类型的 Watcher 对象
      • 没有静态方法,因为 $watch 方法中要使用 Vue 的实例
      • Watcher 分三种:计算属性 Watcher、用户 Watche(侦听器)、渲染 Watcher
        • 创建顺序:计算属性 Watcher、用户 Watcher(侦听器)、渲染 Watcher
      • vm.$watche()
        • src/core/instance/state.js

13、watche——源码

  • initState(vm: Component):src/core/instance/state.js
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)
  }
}
  • initWatch (vm: Component, watch: Object:src/core/instance/state.js
function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
  • createWatcher (vm: Component, expOrFn: string | Function, handler: any, options?: Object):src/core/instance/state.js
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}
  • vm.$watch(expOrFn, handler, options):src/core/instance/state.js
Vue.prototype.$watch = function(
    expOrFn: string | Function,
    cb: any,
    options ? : Object
): Function {
    // 获取 Vue 实例 this
    const vm: Component = this
    if (isPlainObject(cb)) {
        // 判断如果 cb 是对象,执行 createWatcher 
        return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
        // 标记为用户 watcher
    options.user = true
        // 创建用户 watcher 对象
    const watcher = new Watcher(vm, expOrFn, cb, options)
        // 判断 immediate 如果为 true
    if (options.immediate) {
        // 立即执行一次 cb 回调,并且把当前值传入
        try {
            cb.call(vm, watcher.value)
        } catch (error) {
            handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
        }
    }
    // 返回取消监听的方法
    return function unwatchFn() {
        watcher.teardown()
    }
}

14、nextTick——源码

  • 异步更新队列——nextTick()
    • Vue 更新 DOM 是异步执行的,批量的
      • 在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获得更新后的 DOM
    • vm.$nextTick(function(){/* 操作DOM */}) / Vue.nextTick(function(){})
  • vm.$nextTick 代码演示
<div id="app">
    <p ref='p1'>{{msg}}</p>
</div>

<script src="../../dist/vue.js"></script>
<script>
    const vm = new Vue({
            el: '#app',
            data: {
                msg: 'Hello nextTick',
                name: 'Vue.js',
                title: 'Title'
            },
            mounted() {
                this.msg = 'Hello World'
                this.name = 'Hello snambdom'
                this.title = 'Vue.js'
                this.$nextTick(() => {
                    console.log(this.$refs.p1.textContent)
                })
            }
        })
</script>
  • 源码
    • 定义位置
      • src/core/instance/render.js
      Vue.prototype.$nextTick = function(fn: Function) {
          return nextTick(fn, this)
      }
      
    • 流程:
      • 手动调用 vm.$nextTick()
      • 在 Watcher 的 queueWatcher 中执行 nextTick()
      • src/core/util/next-tick.js
      export function nextTick(cb? : Function, ctx? : Object) {
          let _resolve
              // 把 cb 加上异常处理存入 callbacks 数组中
          callbacks.push(() => {
              if (cb) {
                  try {
                      // 调用 cb()
                      cb.call(ctx)
                  } catch (e) {
                      handleError(e, ctx, 'nextTick')
                  }
              } else if (_resolve) {
                  _resolve(ctx)
              }
          })
          if (!pending) {
              pending = true
                  // 调用
              timerFunc()
          }
          // $flow-disable-line
          if (!cb && typeof Promise !== 'undefined') {
              // 返回 promise 对象
              return new Promise(resolve => {
                  _resolve = resolve
              })
          }
      }
      

你可能感兴趣的:(前端,vue,javascript,vue)