new Vue做了什么?

579 阅读2分钟

前言

面试中很容易问你聊聊Vue的流程,我们从源码分析new Vue过程做了哪些事情,一个大体的流程。然后,我个人根据源码画了一个思维导图。

Xnip2021-12-05_07-35-01.jpeg

代码分析

我们clone一份vue源码,根据package.json找到入口文件。

  • 脚本命令"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
  • 全局搜索web-full-dev, 找到entry: resolve('web/entry-runtime-with-compiler.js')

入口文件

src/platforms/web/entry-runtime-with-compiler.js

这个文件主要是解析组件里手写的template,如:Vue.component('test', {template: "<div>test</div>"}),options上不存在render函数,template模板会被编译成render函数,我们平时写的xxx.vue文件是webpack工程通过vue-loader插件解析的。

import Vue from './runtime/index'
const mount = Vue.prototype.$mount
// 覆盖默认的$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  const options = this.$options
  if (!options.render) {
    let template = options.template
    if (template) {
      // 编译开始
      const { render, staticRenderFns } = compileToFunctions(template, {...}, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  return mount.call(this, el, hydrating)
}

$mount函数

这个文件下主要是实现了挂载方法,传入一个回调函数updateComponent给Watcher,数据发生变化时执行dom的挂载或更新。核心就是执行vm._update(vm._render()) vm._render执行的就是options.render函数,获取虚拟node vm._update主要就是通过vm.__patch__将虚拟dom转化成真实dom src/platforms/web/runtime/index.js

import Vue from 'core/index'
Vue.prototype.__patch__ = inBrowser ? patch : noop
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  callHook(vm, 'beforeMount')

  let 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
}

初始化全局API

这个文件下面主要实现:

  • Vue上挂载静态方法,如set、delete、nextTick、use、mixin、extend等
  • Vue静态options属性上,添加components、directives、filters等属性
  • 注册全局组件,KeepAlive组件

src/core/index.js

import Vue from './instance/index'
initGlobalAPI(Vue)

function initGlobalAPI(Vue: GlobalAPI) {
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive,
  };

  Vue.set = set;
  Vue.delete = del;
  Vue.nextTick = nextTick;

  Vue.observable = (obj: T): T => {
    observe(obj);
    return obj;
  };

  Vue.options = Object.create(null);
  ASSET_TYPES.forEach((type) => {
    Vue.options[type + "s"] = Object.create(null);
  });

  Vue.options._base = Vue;
  extend(Vue.options.components, builtInComponents);

  initUse(Vue);
  initMixin(Vue);
  initExtend(Vue);
  initAssetRegisters(Vue);
}

Vue构造函数

这个文件实现的是Vue的构造函数,原型上添加对应的属性和方法

  • 添加_init方法
  • 添加datadata和props
  • 添加自定义事件,onon、once、offoff、emit
  • 添加渲染相关的事件_update、_render src/core/instance/index.js
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

initMixin(Vue)

添加_init方法,new Vue时执行

// new Vue核心就是执行这些方法
Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    vm._isVue = true
    // 合并options
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
   
    vm._renderProxy = vm
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate') // 执行beforeCreate生命周期
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created') // 执行created生命周期
    // 挂载
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
  
  // 设置父子关系
 function initLifecycle (vm: Component) {
  const options = vm.$options
  let parent = options.parent  // activeInstance
  // 将自身实例添加到父实例的$children中
  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
  ...
}

// 父组件使用子组件时注册的事件,在渲染子组件时,会通过$on('xxx'),在子组件内部进行注册,实现父子通信
function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
      target = vm
      updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
      target = undefined
  }
}

function initRender (vm: Component) {
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode 
  const renderContext = parentVnode && parentVnode.context
  // 获取插槽的信息
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // 此处的$createElement就是h,创建虚拟dom
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  
  const parentData = parentVnode && parentVnode.data
  // 获取父组件传入的未定义到props中的属性$attrs和注册的事件$listeners
  defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
  defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}

// inject
export function initInjections (vm: Component) {
  const result = Object.create(null)
  const keys = Object.keys(vm.$options.inject)
  for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      const provideKey = inject[key].from
      let source = vm
      // 递归的中父组件中找到_provided
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
    }
  if (result) {
    Object.keys(result).forEach(key => {
      defineReactive(vm, key, result[key])
    })
  }
}

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

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);
  }
}

stateMixin(Vue)

添加数据相关的属性和方法

function stateMixin(Vue: Class<Component>) {
  const dataDef = {};
  dataDef.get = function () {
    return this._data;
  };
  const propsDef = {};
  propsDef.get = function () {
    return this._props;
  };
  Object.defineProperty(Vue.prototype, "$data", dataDef);
  Object.defineProperty(Vue.prototype, "$props", propsDef);

  Vue.prototype.$set = set;
  Vue.prototype.$delete = del;

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
   // ...
  }
}

lifecycleMixin(Vue)

处理视图,添加三种类型的方法:dom的渲染、dom的更新、dom的销毁

function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode // 渲染vnode赋值给_vnode
    if (!prevVnode) {
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
  }

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  Vue.prototype.$destroy = function () {
    // ...
}

renderMixin(Vue)

添加模板渲染的多种辅助方法、$nextTick方法、_render方法(获取虚拟dom)

renderMixin (Vue) {
  installRenderHelpers(Vue.prototype);

  Vue.prototype.$nextTick = function (fn) {
    return nextTick(fn, this)
  };

  Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;
    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      );
    }

    vm.$vnode = _parentVnode;
    var vnode;
    try {
      currentRenderingInstance = vm;
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
    } finally {
      currentRenderingInstance = null;
    }
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0];
    }
    if (!(vnode instanceof VNode)) {
      vnode = createEmptyVNode();
    }
    vnode.parent = _parentVnode;
    return vnode
  };
}

总结

new Vue的时候,执行Vue的构造函数,执行_init方法,

  • 1.例添加的父实例的$children中,
  • 2.组件上有父组件的$on方法,则监听该事件
  • 3.实例上添加render函数
  • 4.执行beforeCreate声明周期函数
  • 5.将数据变成响应式
  • 6.执行$mounted函数,首先执行编译后的render函数,然后再将返回的vnode通过patch方法转化成真实dom挂载到页面

最后

最后,祝大家周末快乐,面向薪资编程,在2021年的最后一个月加油冲冲冲~