new Vue初始化流程

170 阅读2分钟

new Vue(options)主要做了 this._init()

image.png

可以看看_init()做了什么?

1、来自于哪里? 来自initMixn中,对Vue.prototype._init()

image.png 2 整个流程

image.png

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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')
  }
  // new Vue的时候主要执行这个_init函数
  this._init(options)
}
initMixin(Vue) // _init函数来自于这里
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
Vue.prototype._init = function (options) {
        let vm = this;
        // 每个vue都会有一个uid
        vm._uid = uid++
        // 把一些全局的api方法混入到当前实例的$options上面
        vm.$options = mergeOptions(vm.constructor.options, options)
        //生命周期钩子beforeCreate
        callHook(vm, 'beforeCreate')
        //初始化状态,props,methods,data,computed,watch
        initState(vm)
        //初始化成功后调用created钩子
        callHook(vm, 'created')
        if (vm.$options.el) {
            //开始挂载
            vm.$mount(vm.$options.el)
        }
    }
Vue.prototype.$mount = function (el) {
		//根据用户传入的 el 属性获取节点
        el = el && document.querySelector(el);
        let vm = this;
        //把节点放在 vm.$el 上方便后面使用
        vm.$el = el;
        let options = vm.$options;
        let template
        /**
         * 编译权重:
         * 优先看有没有render函数,如果有直接用
         * 如果没有render函数就看有没有template模板
         * 如果都没有就直接获取el的outerHTML作为渲染模板
         */
        if (!options.render) {
            if (!options.template) {
                template = el.outerHTML
            } else {
                template = vm.$options.template
            }
        }
        if (template) {
            //用 template 生成 render 函数
            let render = compileToFunctions(template)
            options.render = render
        }
        //调用 mount 方法开始渲染页面。
        return mount(this, el)
    }

export function mountComponent(vm, el) {
	//渲染之前调用 beforeMount 生命周期
    callHook(vm, 'beforeMount')
    //创建一个更新渲染函数 ( 用来得到 Vnode 渲染真实 dom )
    let updateComponent = () => {
        vm.update(vm._render())
    }
    //生成一个渲染 watcher 每次页面依赖的数据更新后会调用 updateComponent 进行渲染
    new Watcher(vm, updateComponent, () => {},{
        before () {
            callHook(vm, 'beforeUpdate')
        }
      },true)
    //渲染真实 dom 结束后调用 mounted 生命周期
    callHook(vm, 'mounted')
}

这里就开始真正渲染真实DOM,创建了Watcher,实例化water内部调用了updateComponent

Watcher内部做了什么?

export class Watcher {
    constructor(vm,expOrFn,cb,options) {
        if (typeof expOrFn === 'function') {
        	// 保留 updateComponent 方法
            this.getters = expOrFn
        }
        this.get();
    }
    get() {
        pushTarget(this)
        let value
        // 这里调用了 updateComponent 方法
        value = this.getters.call(this.vm, this.vm);
        popTarget()
        return value
    }
}

vue 初次渲染时 watcher 内部调用了 updateComponent 方法

updateComponent 做了什么?

let updateComponent = () => {
     //获取到虚拟 dom 调用 update 进行渲染 
     vm.update(vm._render())
}
Vue.prototype._render = function () {
     let vm = this
     // 拿到 render 函数
     let render = vm.$options.render;
     // 调用 render 函数得到 Vnode
     return render.call(vm)
}
Vue.prototype.update = function (vnode) {
     let vm = this
     // 获取到上一次的 Vnode 用于 diff 对比
     const prevVnode = vm._vnode
     if (!prevVnode) {
     	 //首次渲染走这里
         vm.$el = patch(vm.$el, vnode)
     } else {
     	 //数据更新驱动视图更新走这里
         vm.$el = patch(prevVnode, vnode)
     }
     //保留 Vnode
     vm._vnode = vnode
}
return function patch(el, vnode, hydrating, removeOnly) {
  	//首次渲染使用 Vnode 创建真实 dom
    createElm(vnode, false, el)
    return vnode.elm
  }
 function createElm (
    vnode, //虚拟dom
    insertedVnodeQueue,
    parentElm, //父节点
  ) {
     // 查看元素 tag 是不是组件,如果是组件就创建组件
    if (createComponent(vnode, insertedVnodeQueue, parentElm)) {
      return
    }
    const data = vnode.data //得到 data 数据
    const children = vnode.children //得到子元素
    const tag = vnode.tag //获取标签名
    vnode.elm = document.createElement(tag)
    if (isDef(tag)) {
   	  //如果有子节点递归渲染子节点
      createChildren(vnode, children, insertedVnodeQueue)
   	  //给父元素插入子元素
      parentElm.appendChild(elm)
    } else if (isTrue(vnode.isComment)) {
      //创建注释节点
      vnode.elm = document.createComment(vnode.text)
      //给父元素插入注释节点
      parentElm.appendChild(elm)
    } else {
      //创建文本节点
      vnode.elm = document.createTextNode(vnode.text)
      //给父元素插入文本节点
      parentElm.appendChild(elm)
    }
  }
function createChildren (vnode, children, insertedVnodeQueue) {
    if (Array.isArray(children)) {
      for (let i = 0; i < children.length; ++i) {
      	//渲染子节点
        createElm(children[i], insertedVnodeQueue, vnode.elm)
      }
    }
  }

参考:zhuanlan.zhihu.com/p/419896443