Vue 实例初始化过程

技术文章 1年前 (2020) 完美者
1,381 0

标签:context   设置   des   mpi   sde   parent   过程   string   watcher   

new Vue() 实例的初始化

Vue.js 是由 原型链 写法来实现的库,其构造函数在 src/core/instance/index.js

    function Vue(options) {
      if (process.env.NODE_ENV !== ‘production‘ && !(this instanceof Vue)) {
        warn(...)
        // 必须是以 new Vue 方式来创建实例
      }
      this._init(options)
    }
    initMixin(Vue) // 定义了 _init 方法
    stateMixin(Vue)
    eventsMixin(Vue)
    lifecycleMixin(Vue) // 定义了 _update 方法,$destroy 方法
    renderMixin(Vue) // 定义了 _render 方法

    function initMixin(Vue) {
    	Vue.prototype._init = function (options) {
        const vm = this // 存储当前实例
        ...
        vm._isVue = true // 确认自身为Vue实例
        if (options && options._isComponent) {
    	  initInternalComponent(vm, options) // 对于组件传入的options的合并
        } else {
    	 vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          ) // mergeOptions 将传入的options和构造函数的options合并到实例本身的$options
        }
        ... // 一些实例自身的初始化(包括beforeCreate/created钩子的触发)
        if (vm.$options.el) {
          // 如果合并之后的选项中有 el,则将其进行挂载
          vm.$mount(vm.$options.el)
        }
      }
    }

在附带 compiler(编译器)的版本中,$mount 的实现方式如下

位置:src/platform/web/entry-runtime/with-compiler.js

    const mount = Vue.prototype.$mount
    // hydrating == false
    Vue.prototype.$mount = function (el, hydrating) {
      const options = this.$options
      if (!options.render) {
    	// 传入的options没有render函数,就通过template或者el,编译成render函数并赋值给 options.render
      }
      return mount.call(this, el, hydrating) // 调用原来的 $mount 方法, return vm
    }

    // src/platform/web/runtime/index.js 原$mount方法
    Vue.prototype.$mount = function(el, hydrating) {
      el = el && inBrowser ? query(el) : undefined // 通过传入的 el 来选择对应的容器元素
      return mountComponent(this, el) // return vm
    }
    
    // src/core/instance/lifecycle.js (经简化)
    function mountComponent(vm, el) {
      vm.$el = el // 缓存 el
      ...
      callHook(vm, ‘beoforeMount‘)
      let updateComponent = () => {
        vm._update(vm._render())
      }
      // 创建 vm 对应的一个 渲染Watcher
      new Watcher(vm, updateComponent, noop, {
        before() {
          if (vm._isMounted && !vm._isDestroyed) {
            callHook(vm, ‘beforeUpdate‘)
          }
        }
      }, true /* 渲染watcher标志 */)
      
      if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, ‘mounted‘)
      }
      return vm
    }

创建 渲染Watcher 的过程

    // src/core/observer/watcher.js
    class Watcher {
    	constructor(vm, expOrFn, cb, options, isRenderWatcher) {
        this.vm = vm // Vue 实例
        if (isRenderWatcher) {
          vm._watcher = this
        }
        vm._watchers.push(this)
        ...
        if (typeof expOrFn === ‘function‘) {
          this.getter = expOrFn // this.getter 其实就是定义Water时候传入的 updateComponent
        } else {
          ...
        }
        this.value = this.lazy ? undefined : this.get() // this.lazy == false
         // 所以在赋值 this.value 的过程中,this.get()执行过程中,传入的 getter 函数执行了一次
      }
        
      get() {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
          value = this.getter.call(vm, vm)
        } catch (e) {
          ...
        } finally {
          if (this.deep) {
            traverse(value)
          }
          popTarget()
          this.cleanupDeps()
        }
        return value
      }
    }

分析 updateComponent 函数

    // 定义
    let updateComponent = () => {
      vm._update(vm._render()) // 参数为 vnode
    }
    // 先分析 _render()
    // src/core/instance/render.js
    function renderMixin(Vue) {
    	Vue.prototype._render = function () {
        const vm = this
        const { render, _parentVnode } = vm.$options
        if (_parentVnode) {
        ...
          // 设置 vm 的插槽
        }
        vm.$vnode = _parentVnode // 存储 父vnode 节点
        let vnode
        try {
          currentRenderingInstance = vm
          vnode = render.call(vm._renderProxy, vm.$createElement)
          // vm._renderProxy.render(vm.$createElement)
          // vm._renderProxy == vm
        } catch (e) {}
        ...
        vnode.parent = _parentVnode
        return vnode
        // 返回一个由 编译/自带的 render 函数执行得到的 vnode
      }
    }
      
    // render 函数的调用其实就是 $createElement 的执行
    // 同一个文件中有这样的定义
    // initRender 函数中
    // 在 _init 方法初始化过程中,会调用initRender(vm)
    vm.$createElement = (a,b,c,d) => createElement(vm,a,b,c,d,true)
    // createElement 定义在 src/core/vdom/create-element.js

    // src/core/vdom/create-element.js
    function createElement(vm, tag, data, children, normalizationType, alwaysNormalize) {
    	if (Array.isArray(data) || isPrimitive(data)) {
        // 参数重载
        normalizationType = children
        children = data
        data = undefined
      }
      return _createElement(context, tag, data, normalizationType) // vnode
    }
    
    function _createElement(context, tag, data, children, normalizationType) {
    	...
      // 将children 拍平成一维数组
      if (normalizationType === ALWAYS_NORMALIZE) {
        children = normalizeChildren(children)
      } else if (normalizationType === SIMPLE_NORMALIZE) {
        children = simpleNormalizeChildren(children)
      }
      
      let vnode, ns
      if (typeof tag === ‘string‘) {
        // 适用于传入一个由 vue-loader 编译生成或者按格式编写 tag
        if (config.isReservedTag(tag)) {
          // 平台原有的 tag  标签,如H5的div等
          vnode = new VNode(
          	config.parsePlatform(tag), data, children, 
            undefined, undefined, context
          )
        } else if (
          (!data || !data.pre) && 
          isDef(Ctor = resolveAssest(context.$options, ‘components‘ ,tag))
        ) {
          // 创建组件的vnode
    			vnode = createComponent(Ctor, data, context, children, tag) // return vnode
        }
      } else {
        // 适用于直接传入一个导出的 .vue 文件到render函数
        // e.g. render: h => { return h(App) }
        // 此处 h 相当于 createElement
        // tag == App
    	vnode = createComponent(tag, data, context, children) // return vnode
      }
      if (Array.isArray(vnode)) {
        return vnode
      } else if (isDef(vnode)) {
        ...
        return vnode
      } else {
        return createEmptyVNode() // 空白vnode
      }
    }
    // 因此 vm._render() 函数会生成 vm 对应的 vnode 并返回给 vm._update 函数
    
    // 分析 createComponent 函数
    // src/core/vdom/create-component.js
    function createComponent (Ctor, data, context, children, tag) {
      if (isUndef(Ctor)) { return } // 没有传入构造函数/信息
      const baseCtor = context.$options._base // 其实就是Vue,在合并选项的时候会合并进去
    	if (isObject(Ctor)) {
        Ctor = baseCtor.extend(Ctor) // 传入的构造信息通过 Vue.extend 转为构造函数
      }
      ...
      installComponentHooks(data)
      // 安装组件钩子,合并到data对象
      /*
        data = {
          on: {...},
          hook: {...}
        }
      */
      const name = Ctor.options.name || tag
      const vnode = new VNode(
      	`vue-component-${Ctor.c_id}${name ? `-${name}` : ‘‘}`,
        data ,undefined, undefined, undefined, context, 
        { Ctor, propsData, listeners, tag, children }, // 该对象为componentOptions参数
        asyncFactory
      )
      return vnode
    }

再来看看 vm._update

    // src/core/instance/lifecycle.js
    function lifecycleMixin(Vue) {
      Vue.prototype._update = function (vnode, hydrating) { // hydratng == false
        const vm = this
        const prevEl = vm.$el // 在 mountComponent 函数中缓存
        const prevVnode = vm._vnode
        vm._vnode = vnode // 缓存当前vnode 到实例的 _vnode属性
        const restoreActiveInstance = setActiveInstance(vm) 
        // 存储当前的 vm 实例到 activeInstance
        // 组件实际插入到 DOM 对象是在 __patch__ 过程中
        if (!prevVnode) {
          vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
        } else {
          vm.$el = vm.__patch__(prevVnode, vnode)
        }
        restoreActiveInstance() // 重新释放当前 vm 实例,activeInstance 回退到上一个 vm 实例
        ...
        // 根组件直接将 $el 更新到 父实例 的$el 
        // e.g. render: h => h(App) 最后会将 App 实例 patch 得到的 $el 更新到根实例(new Vue) 上
        if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
          vm.$parent.$el = vm.$el
        }
      }
    }
    // 至此实例的渲染 watcher 创建完成,实例挂载结束

下面是组件如何 实际插入到DOM对象 的过程分析,在 patch 函数中,

    // src/platforms/web/runtime/index.js
    Vue.prototype.__patch__ = patch
    // 这个patch 函数就是实际上调用的函数
    // 经分析最后 patch 函数在 src/core/vdom/patch.js 的 createPatchFunction 中返回
    // 简化版 patch
    function patch (oldVnode, vnode, hydrating, removeOnly) {
    	if (isUndef(vnode)) {
        // 只有传入了旧节点的信息
        if (isDef(oldVnode)) invokeDestroy(oldVnode) // 销毁旧节点
      }
      let isInitialPatch = false // 是否为根节点
      const insertedVnodeQueue = []
      if (isUndef(oldVnode)) {
        isInitialPatch = true
        crateElm(vnode, insertedVnodeQueue)
      } else {
        const isRealElement = isDef(oldVnode.nodeType)
        //  h(App)是传入的 oldVnode 是 el
        if (!isRealElement && sameVndoe(oldVnode, vnode)) {
          patchVnode(oldVnode, vnode, insertedVnodeQueue)
        } else {
          if (isRealElement) {
            ...
            oldVnode = emptyNodeAt(oldVnode) // 用 el 元素创建一个 oldVnode
          }
          const oldElm = oldVnode.elm // 就是传入的 el元素
          const parentElm = nodeOps.parentNode(oldElm) // el的父元素
          
          createElm(
            vnode,
            insertedVnodeQueue,
            oldElm._leaveCB ? null : parentElm,
            nodeOps.nextSibling(oldElm) // oldElm 的下一个兄弟节点
          )
          ...
          if (isDef(parentElm)) {
            removeVnodes([oldVnode], 0, 0)
          } else if (isDef(oldVnode.tag)) {
            invokeDestroyHook(oldVnode)
          }
        }
      }
      invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) 
      // 通过insert钩子触发组件的mounted钩子
      return vnode.elm // elm元素 返回给__patch__ ,赋值给vm.$el
    }

重点分析 createElm 函数

    function createElm (
      vnode,
       insertedVnodeQueue,
       parentElm,
       refElm,
       nested,
       ownerArray,
       index
    ) {
        // vnode直接创建一个组件
        if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
          return
        }
        
        const data = vnode.data
        const children = vnode.children
        const tag = vnode.tag
        if (isDef(tag)) {
          ...
          // 利用 vnode 的 tag 属性直接生成一个原生元素(div)
          vnode.elm = nodeOps.createElement(tag, vnode)
        }
        createChildren(vnode, children, insertedVnodeQueue)
        // 创建子节点,实际上也是调用 createElm
        if (isDef(data)) {
          // 插入vnode序列
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        // 插入到父节点
        insert(parentElm, vnode.elm, refElm)
      } else if (isTrue(vnode.isComment)) {
        // 注释节点创建
        vnode.elm = nodeOps.createComment(vnode.text)
        insert(parentElm, vnode.elm, refElm)
      } else {
        // 文本节点
        vnode.elm = nodeOps.createTextNode(vnode.text)
        insert(parentElm, vnode.elm, refElm)
      }
    
    // createChildren
    function createChildren (vnode, children, insertedVnodeQueue) {
      if (Array.isArray(children)) {
        // 只接受数组类型
        if (process.env.NODE_ENV !== ‘production‘) {
          checkDuplicateKeys(children)
        }
        for (let i = 0; i < children.length; ++i) {
          // 实际上将父 vnode 的 elm 作为父元素传入
          createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
        }
      } else if (isPrimitive(vnode.text)) {
        nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
      }
    }
    
    // createComponent
    function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
      let i = vnode.data
      if (isDef(i)) {
        const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
        if (isDef(i = i.hook) && isDef(i = i.init)) {
          i(vnode, false /* hydrating */) 
          // 调用hooks中的init钩子,在 create-component.js中componentVNodeHooks
        }
        if (isDef(vnode.componentInstance)) {
          initComponent(vnode, insertedVnodeQueue)
          insert(parentElm, vnode.elm, refElm) // 插入到父元素 DOM
          if (isTrue(isReactivated)) {
            reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
          }
          return true
        }
      }
    }
    
    // src/core/vdom/create-component.js
    // init钩子函数
    function init (vnode, hydrating) {
      if (
        vnode.componentInstance &&
        !vnode.componentInstance._isDestroy && 
        vnode.data.keepAlive
      ) {
        ...
      } else {
        // createComponentInstanceForVnode 函数返回一个vm实例
        // 实际上是调用了 vnode 的componentOptions.Ctor 来构造子组件
        const child = vnode.componentInstance = createComponentInstanceForVnode(
          vnode,
          activeInstance
        )
        // 上面生成的实例挂载(el是undefined)
        child.$mount(hydrating ? vnode.elm : undefined, htdrating) // hydrating == false
      }
    }
      
    // createComponentInstanceForVnode
    // parent为当前的父vm,就是需要创建的vm的父vm == activeInstance
    function createComponentInstanceForVnode (vnode, parent) {
      const options = {
        _isComponent: true,
        _parentVnode: vnode,
        parent
      }
      ...
      return new vnode.componentOptions.Ctor(options)
      // Sub 构造函数的实例化 return vm
      // 重新走一次 _init 流程
    }
new Vue - vm.mount - mountComponent(vm) - vm._render(vmVnode) - vm.componentOptions - vm._update(vmVnode) - patch - createElm(vmVnode) - createComponent(vmVnode) - vm.hook.init - new child - vm.componentInstance - child.mount- createComponent(vmVnode) - insert

new child - child.mount - mountComponent(child) - child._update(childVnode) - patch - createElm(childVnode) - createElement(childVnode) - insert(child)

Vue 实例初始化过程

标签:context   设置   des   mpi   sde   parent   过程   string   watcher   

原文地址:https://www.cnblogs.com/garfill/p/13649252.html

版权声明:完美者 发表于 2020-09-17 21:27:26。
转载请注明:Vue 实例初始化过程 | 完美导航

暂无评论

暂无评论...