• Vue2.x源码学习笔记-从一个小例子查看vm实例生命周期


    学习任何一门框架,都不可能一股脑儿的从入口代码从上到下,把代码看完,

    这样其实是很枯燥的,我想也很少有人这么干,或者这么干着干着可能干不下去了。

    因为肯定很无聊。

    我们先从一个最最简单的小例子,来查看new Vue(options)实例,这个过程发生了什么。

    vm实例上的属性又如何添加上去的,又如何渲染到浏览器页面上的。

    关于vue的数据依赖和虚拟dom都是重点,必然会在以后的帖子记录。

    这篇帖子就根据下例子,看看实例化一个vm实例做了啥吧。

    先把小例子贴出来:

          <div id="app">
              <p>这是<span>静态内容</span></p>
              <p>{{message}}</p>
          </div>
          <script src="../../dist/vue.js"></script>
          <script>
              var vm = new Vue({
                  el: '#app',
                  data: {
                      message: 'hi vue!'
                  }
              })
              console.log(vm)
          </script>

    根据上篇介绍了vue的调式笔记,那我们快快进入源码吧

    根据vue构造函数那篇笔记,我们知道了Vue原型上有哪些方法,_init方法就是其中一个方法

    我们看到_init就把实例要做的事情都做完了,当然其中有的语句所做的事,太多了。我们先一点一点开see see吧。

    看图不好玩,我把源码取出 来 好好瞧瞧

    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid vm实例唯一标识
        vm._uid = uid++
    
        let startTag, endTag
        /* istanbul ignore if 性能统计相关 */ 
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-init:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          mark(startTag)
        }
    
        // a flag to avoid this being observed 监听对象变化时用于过滤vm
        vm._isVue = true
        // merge options  _isComponent是内部创建子组件时才会添加为true的属性
        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 {
          // mergeOptions 方法 合并构造器及构造器父级上定义的options
          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
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate')
        initInjections(vm) // resolve injections before data/props
        initState(vm)
        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(`${vm._name} init`, startTag, endTag)
        }
    
        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    }

    1. 给实例添加了唯一标识uid

    2.性能统计相关,先忽略

    3. 给实例添加_isVue,监听对象变化时用于过滤vm

    4. 选项对象如果有_isComponent,就初始化内部组件,_isComponent是内部创建子组件时才会添加为true的属性

    5. 小例子会走分支,mergeOptions 方法 合并构造器及构造器父级上定义的options,resolveConstructorOptions方法后面笔记会详解,

      mergeOptions方法接受3个参数。我们先简单看下resolveConstructorOptions方法的定义

    export function resolveConstructorOptions (Ctor: Class<Component>) {
      let options = Ctor.options
      // 有super属性,说明Ctor是通过Vue.extend()方法创建的子类
      if (Ctor.super) {
        const superOptions = resolveConstructorOptions(Ctor.super)
        const cachedSuperOptions = Ctor.superOptions
        if (superOptions !== cachedSuperOptions) {
          // super option changed,
          // need to resolve new options.
          Ctor.superOptions = superOptions
          // check if there are any late-modified/attached options (#4976)
          const modifiedOptions = resolveModifiedOptions(Ctor)
          // update base extend options
          if (modifiedOptions) {
            extend(Ctor.extendOptions, modifiedOptions)
          }
          options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
          if (options.name) {
            options.components[options.name] = Ctor
          }
        }
      }
      return options
    }

    可以看出Ctor.options其实就是Vue构造函数自身,在Vue构造函数静态属性那篇笔记,Vue是拥有options属性的,且有截图,等下会再截图看下,

    接着在该函数中有个if语句,我们小例子会跳过的,直接返回options。因为有super属性,说明Ctor是通过Vue.extend()方法创建的子类。那么

    options是啥呢,如下图,

    回到_init方法中,mergeOptions方法的第二个参数就是我们传入的options,第三个参数就是vm实例,把参数一起截个图吧,好回忆

    mergeOptions是Vue中处理属性的合并策略的地方, 先看下它的定义

    export function mergeOptions (
      parent: Object,
      child: Object,
      vm?: Component
    ): Object {
      if (process.env.NODE_ENV !== 'production') {
        // 如果有options.components,则判断是否组件名是否合法
        checkComponents(child)
      }
      // 格式化child的props
      normalizeProps(child)
      // 格式化child的directives
      normalizeDirectives(child)
      const extendsFrom = child.extends
      if (extendsFrom) {
        parent = typeof extendsFrom === 'function'
          ? mergeOptions(parent, extendsFrom.options, vm)
          : mergeOptions(parent, extendsFrom, vm)
      }
      if (child.mixins) {
        for (let i = 0, l = child.mixins.length; i < l; i++) {
          let mixin = child.mixins[i]
          if (mixin.prototype instanceof Vue) {
            mixin = mixin.options
          }
          parent = mergeOptions(parent, mixin, vm)
        }
      }
      const options = {}
      let key
      for (key in parent) {
        mergeField(key)
      }
      for (key in child) {
        if (!hasOwn(parent, key)) {
          mergeField(key)
        }
      }
      function mergeField (key) {
        const strat = strats[key] || defaultStrat
        options[key] = strat(parent[key], child[key], vm, key)
      }
      return options
    } 

    该函数主要返回一个数据合并后的options,
    我们的小例子比较简单, so关于判断是否组件名是否合法,
    格式化child的props, 格式化child的directives
    extends, mixins 先跳过。
    我们直接看怎么把属性合并到options = {}这个对象上的
    首先遍历parent对象,然后通过mergeField函数,把components,
    directives, filters, _base属性先添加到options对象上,值为
    strats对象上的静态方法。
    然后遍历child对象,把el, data属性也添加到options = {} 这个对象上
    值为strats对象上对应的静态方法。
    那我们先看看strats这个对象上有哪些静态方法,源码如下(src/util/options.js)

    const strats = config.optionMergeStrategies
    
    if (process.env.NODE_ENV !== 'production') {
      strats.el = strats.propsData = function (parent, child, vm, key) {
         /**/
      }
    }
    
    strats.data = function (
      parentVal: any,
      childVal: any,
      vm?: Component
    ): ?Function {
      /**/
    }
    
    function mergeHook (
      parentVal: ?Array<Function>,
      childVal: ?Function | ?Array<Function>
    ): ?Array<Function> {
      return childVal
        ? parentVal
          ? parentVal.concat(childVal)
          : Array.isArray(childVal)
            ? childVal
            : [childVal]
        : parentVal
    }
    
    config._lifecycleHooks.forEach(hook => {
      strats[hook] = mergeHook
    })
    
    function mergeAssets (parentVal: ?Object, childVal: ?Object): Object {
      /**/
    }
    
    config._assetTypes.forEach(function (type) {
      strats[type + 's'] = mergeAssets
    })
    
    strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
      /* istanbul ignore if */
      /**/
    }
    
    strats.props =
    strats.methods =
    strats.computed = function (parentVal: ?Object, childVal: ?Object): ?Object {
      if (!childVal) return Object.create(parentVal || null)
      if (!parentVal) return childVal
      /**/
    }
    
    const defaultStrat = function (parentVal: any, childVal: any): any {
      return childVal === undefined
        ? parentVal
        : childVal
    }

    以上是缩减版的代码,其实看下截图,会一目了然

    可以看到其实就是我们new Vue(options) 中的options对象中的可选参数。我们小例子只传了el, data,

    我们看看通过mergeOptions方法合并后的options长的什么鸟样,如图:

    其实小例子只是走个过程,没必要把所有函数代码弄懂,先把大体流程走完,后续代码在一一分析。

    6. 回到vm_init()方法中,接着走initProxy(vm)这个语句,这个语句其实就是给vm实例添加了一个_renderProxy属性,值为为一个Proxy代理对象,生产环境就是vm自身。

    接下来的每个语句都有好多代码啊,我们一个个 look see see

     7. initLifecycle 方法的定义

    export function initLifecycle (vm: Component) {
      const options = vm.$options
    
      // locate first non-abstract parent
      let parent = options.parent
      if (parent && !options.abstract) {
        while (parent.$options.abstract && parent.$parent) {
          parent = parent.$parent
        }
        parent.$children.push(vm)
      }
    
      vm.$parent = parent
      vm.$root = parent ? parent.$root : vm
    
      vm.$children = []
      vm.$refs = {}
    
      vm._watcher = null
      vm._inactive = null
      vm._directInactive = false
      vm._isMounted = false
      vm._isDestroyed = false
      vm._isBeingDestroyed = false
    } 

    初始化生命周期,该函数只是给vm添加了
    $parent, $root, $children, $refs, _watcher,
    _inactive, _directInactive, _isMounted, _isDestroyed
    _isBeingDestroyed属性。

    options.abstract用于判断是否是抽象组件,
    组件的父子关系建立会跳过抽象组件,抽象组件比如keep-alive、transition等。
    所有的子组件$root都指向顶级组件。

    8. initEvents方法的定义

    export function initEvents (vm: Component) {
      vm._events = Object.create(null)
      vm._hasHookEvent = false
      // init parent attached events
      const listeners = vm.$options._parentListeners
      if (listeners) {
        updateComponentListeners(vm, listeners)
      }
    } 

    该方法初始化事件相关的属性,给vm实例添加了_events, _hasHookEvent属性
    _parentListeners是父组件中绑定在自定义标签上的事件,供子组件处理。

    9. initRender方法的定义

    export function initRender (vm: Component) {
      vm.$vnode = null // the placeholder node in parent tree
      vm._vnode = null // the root of the child tree
      vm._staticTrees = null
      const parentVnode = vm.$options._parentVnode
      const renderContext = parentVnode && parentVnode.context
      vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
      vm.$scopedSlots = emptyObject
      // bind the createElement fn to this instance
      // so that we get proper render context inside it.
      // args order: tag, data, children, normalizationType, alwaysNormalize
      // internal version is used by render functions compiled from templates
      vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
      // normalization is always applied for the public version, used in
      // user-written render functions.
      vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
    }

    这里先给vm添加了$vnode, _vnode, _staticTrees, $slots, $scopedSlots, _c, $createElement
    属性或者方法(添加了一些虚拟dom、slot等相关的属性和方法)

    10. 调用beforeCreate钩子

    11. 调用initInjections(vm)方法,我们小例子比较简单,不会进入if语句中

    export function initInjections (vm: Component) {
      const inject: any = vm.$options.inject
      if (inject) {
        // inject is :any because flow is not smart enough to figure out cached
        // isArray here
        const isArray = Array.isArray(inject)
        const keys = isArray
          ? inject
          : hasSymbol
            ? Reflect.ownKeys(inject)
            : Object.keys(inject)
    
        for (let i = 0; i < keys.length; i++) {
          const key = keys[i]
          const provideKey = isArray ? key : inject[key]
          let source = vm
          while (source) {
            if (source._provided && provideKey in source._provided) {
              /* istanbul ignore else */
              if (process.env.NODE_ENV !== 'production') {
                defineReactive(vm, key, source._provided[provideKey], () => {
                  warn(
                    `Avoid mutating an injected value directly since the changes will be ` +
                    `overwritten whenever the provided component re-renders. ` +
                    `injection being mutated: "${key}"`,
                    vm
                  )
                })
              } else {
                defineReactive(vm, key, source._provided[provideKey])
              }
              break
            }
            source = source.$parent
          }
        }
      }
    }

    将父组件provide中定义的值,通过inject注入到子组件,且这些属性不会被观察

    12. initState(vm)

    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) initWatch(vm, opts.watch)
    }

    该方法主要就是操作数据了,props、methods、data、computed、watch,
    从这里开始就涉及到了Observer、Dep和Watcher,下个笔记再记录

    13. initProvide(vm)

     export function initProvide (vm: Component) {
       const provide = vm.$options.provide
       if (provide) {
         vm._provided = typeof provide === 'function'
           ? provide.call(vm)
           : provide
       }
     }

    也不会进分支,先略过

    14. 调用created钩子函数。

    可以看到在created钩子函数调用前, 基本就是对传入数据的格式化、数据的双向绑定、以及一些属性的初始化。

    现在来看下实例的属性和方法

    接下来看看怎么把html模板中的属性出来的

    15. vm.$mount(vm.$options.el)

    const mount = Vue.prototype.$mount
    // 重写Vue构造函数原型上的$mount方法
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      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 <html> or <body> - mount to normal elements instead.`
        )
        return this
      }
    
      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) {
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
            mark('compile')
          }
    
          const { render, staticRenderFns } = compileToFunctions(template, {
            shouldDecodeNewlines,
            delimiters: options.delimiters
          }, this)
          options.render = render
          options.staticRenderFns = staticRenderFns
    
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
            mark('compile end')
            measure(`${this._name} compile`, 'compile', 'compile end')
          }
        }
      }
      return mount.call(this, el, hydrating)
    }

    该方法主要拿到template模板,然后通过compileToFunctions方法的返回值给vm实例
    的$options添加render属性,值为一个匿名函数该匿名函数返回值为:
    with(this){return _c('div',{attrs:{"id":"app"}},[_m(0),_v(" "),_c('p',[_v(_s(message))])])}
    还添加了一个staticRenderFns属性,值为一个数组,数组元素为匿名函数:

    anonymous() {
    with(this){return _c('p',[_v("这是"),_c('span',[_v("静态内容")])])}
    }

    至于compileToFunctions函数先不拿出来看了,目前先知道它干了啥,就行了(至于实例的_c,_v,_m这些方法何时挂载上去的,前面笔记已经说过了)

    之后调用 mount.call(this, el, hydrating) 方法

    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && inBrowser ? query(el) : undefined
      return mountComponent(this, el, hydrating)
    } 

    该方法又调用mountComponent(this, el, hydrating)

    export function mountComponent (
      vm: Component,
      el: ?Element,
      hydrating?: boolean
    ): Component {
      vm.$el = el
      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
            )
          }
        }
      }
      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(`${name} render`, startTag, endTag)
    
          mark(startTag)
          vm._update(vnode, hydrating)
          mark(endTag)
          measure(`${name} patch`, startTag, endTag)
        }
      } else {
        updateComponent = () => {
          vm._update(vm._render(), hydrating)
        }
      }
    
      vm._watcher = new Watcher(vm, updateComponent, noop)
      hydrating = false
    
      // manually mounted instance, call mounted on self
      // mounted is called for render-created child components in its inserted hook
      if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
      }
      return vm
    }

    小例子在该方法大致流程是这么走的

    因为vm.$options.render就是给匿名函数,所以不会走if分支,
    然后调用beforeMount钩子函数
    再然后定义一个updateComponent函数,这个函数怎么执行是个关键
    然后给实例添加了一个_watcher属性,值为Watcher实例
    然后如果vm.$vnode == null则把vm._isMounted变量置为true,然后调用mounted钩子函数
    最后返回vm实例,可以链式调用。

    触发updateCOMPONENT函数是new Watcher,先看看Watcher类的定义

    constructor (
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: Object
      ) {
        this.vm = vm
        vm._watchers.push(this)
        if (options) {
          this.deep = !!options.deep
          this.user = !!options.user
          this.lazy = !!options.lazy
          this.sync = !!options.sync
        } else {
          this.deep = this.user = this.lazy = this.sync = false
        }
        ...
        this.expression = process.env.NODE_ENV !== 'production'
          ? expOrFn.toString()
          : ''
    
        if (typeof expOrFn === 'function') {
          this.getter = expOrFn
        } else {
          this.getter = parsePath(expOrFn)
          if (!this.getter) {
            this.getter = function () {}
            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()
      }
    
      get () {
        pushTarget(this)
        let value
        const vm = this.vm
        if (this.user) {
          try {
            value = this.getter.call(vm, vm)
          } catch (e) {
            handleError(e, vm, `getter for watcher "${this.expression}"`)
          }
        } else {
          value = this.getter.call(vm, vm)
        }
    
        if (this.deep) {
          traverse(value)
        }
        popTarget()
        this.cleanupDeps()
        return value
      }

    在构造函数中,expOrFn也就是updateComponent赋值给this.getter,
    并且在获取this.value的值时会调用this.get(),这里的this.lazy默认值是false,
    在computed属性中创建的Watcher会传入true。

    在this.get()中,会调用this.getter,所以上面的例子中,updateComponent方法会被调用,

    updateComponent = () => {
        vm._update(vm._render(), hydrating)
    }

    回到该函数,先执行实例的_render函数,该函数主要生成虚拟dom
    然后执行实例的update方法

    Vue.prototype._render = function (): VNode {
        const vm: Component = this
        const {
          render,
          staticRenderFns,
          _parentVnode
        } = vm.$options
    
        if (vm._isMounted) {
          // clone slot nodes on re-renders
          for (const key in vm.$slots) {
            vm.$slots[key] = cloneVNodes(vm.$slots[key])
          }
        }
    
        vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject
    
        if (staticRenderFns && !vm._staticTrees) {
          vm._staticTrees = []
        }
        // set parent vnode. this allows render functions to have access
        // to the data on the placeholder node.
        vm.$vnode = _parentVnode
        // render self
        let vnode
        try {
          vnode = render.call(vm._renderProxy, vm.$createElement)
        } catch (e) {
          handleError(e, vm, `render function`)
          // return error render result,
          // or previous vnode to prevent render error causing blank component
          /* istanbul ignore else */
          if (process.env.NODE_ENV !== 'production') {
            vnode = vm.$options.renderError
              ? vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
              : vm._vnode
          } else {
            vnode = vm._vnode
          }
        }
        // return empty vnode in case the render function errored out
        if (!(vnode instanceof VNode)) {
          if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
            warn(
              'Multiple root nodes returned from render function. Render function ' +
              'should return a single root node.',
              vm
            )
          }
          vnode = createEmptyVNode()
        }
        // set parent
        vnode.parent = _parentVnode
        return vnode
      }

    该函数主要是 vnode = render.call(vm._renderProxy, vm.$createElement)语句
    函数调用过程中的this,是vm._renderProxy,是一个Proxy代理对象或vm本身。暂且把它当做vm本身。

    _c是(a, b, c, d) => createElement(vm, a, b, c, d, false)。
    createElement函数做了这些事:
    a是要创建的标签名,这里是div。
    接着b是data,也就是模板解析时,添加到div上的属性等。
    c是子元素数组,所以这里又调用了_c来创建一个p标签。
    _v是createTextVNode,也就是创建一个文本结点。
    _s是_toString,也就是把message转换为字符串,在这里,因为有with(this),
    所以message传入的就是我们data中定义的第一个vue实例。
    所以,render函数返回的是一个VNode对象,也就是我们的虚拟dom对象。
    它的返回值,将作为vm._update的第一个参数。

    接着看下update方法

      Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        const vm: Component = this
        if (vm._isMounted) {
          callHook(vm, 'beforeUpdate')
        }
        const prevEl = vm.$el
        const prevVnode = vm._vnode
        const prevActiveInstance = activeInstance
        activeInstance = vm
        vm._vnode = vnode
        // Vue.prototype.__patch__ is injected in entry points
        // based on the rendering backend used.
        if (!prevVnode) {
          // initial render
          vm.$el = vm.__patch__(
            vm.$el, vnode, hydrating, false /* removeOnly */,
            vm.$options._parentElm,
            vm.$options._refElm
          )
        } else {
          // updates
          vm.$el = vm.__patch__(prevVnode, vnode)
        }
        activeInstance = prevActiveInstance
        // update __vue__ reference
        if (prevEl) {
          prevEl.__vue__ = null
        }
        if (vm.$el) {
          vm.$el.__vue__ = vm
        }
        // if parent is an HOC, update its $el as well
        if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
          vm.$parent.$el = vm.$el
        }
        // updated hook is called by the scheduler to ensure that children are
        // updated in a parent's updated hook.
      }

    从mountComponent中知道创建Watcher对象先于vm._isMounted = true。
    所以这里的vm._isMounted还是false,不会调用beforeUpdate钩子函数。

    下面会调用vm.__patch__,在这一步之前,
    页面的dom还没有真正渲染。该方法包括真实dom的创建、虚拟dom的diff修改、dom的销毁等,
    具体细节后续笔记在记录。

  • 相关阅读:
    Shenzhen Wanze Technology Co., Ltd.技术支持
    Shenzhen Wanze Technology Co., Ltd.隐私协议
    123457123456#1#----com.MC.CarWashKidsGames234----前拼后广--洗车游戏mc-mc1111
    123457123457#0#----com.MC.konglongtianse222----前拼后广--恐龙填色mc-mc1111
    123457123456#5#----com.MC.HuaHuaGame866----前拼后广--babyDrawGame-mc555
    ios开发注意事项小总结
    ios开发将截图保存到相册
    iOS评分功能实现
    123457123456#2#----com.MC.DishuGame368----前拼后广--儿童打地鼠Game-mc2222222
    123457123457#0#----com.MC.3or1KongLongPT867----前拼后广--3or1恐龙PtGame-mc
  • 原文地址:https://www.cnblogs.com/sorrowx/p/7979487.html
Copyright © 2020-2023  润新知