vue2源码浅读(二):vue初始化

vue2源码浅读(二):vue初始化

入口文件

  • 通过启动命令:"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap",,找到scripts/config.js文件,在该文件中找到启动入口web-full-dev:
    在这里插入图片描述
  • 找到resolve的定义处:
    在这里插入图片描述
  • 找到aliases定义处:
    在这里插入图片描述
  • 定位到src/platforms/web/entry-runtime-with-compiler.js文件。

vue初始化流程

找到初始化入口文件

  • 可以通过在new Vue之前打debugger,然后单步调试找到vue初始化第一步。然后采用调试的方法依次观察初始化流程。
  • 也可以在entry-runtime-with-compiler.js的文件里找到import Vue from './runtime/index',在/runtime/index.js里又可以找到import Vue from 'core/index',在core/index.js里又可以找到import Vue from './instance/index',自此找到了初始化的入口文件:
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'

// 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')
  }
  // 初始化方法,该方法由initMixin混入
  this._init(options)    // Vue原型上添加了_init的方法, 用户执行Vue函数时就会调用_init方法,_init方法接收用户参数 将用户传入的options绑定到this上 
}
initMixin(Vue)   // 初始化混入
stateMixin(Vue)  // state混入
eventsMixin(Vue)  // event混入
lifecycleMixin(Vue)  // 生命周期混入
renderMixin(Vue)  // 渲染函数混入
export default Vue

_init

  • src\core\instance\init.js
export function initMixin (Vue: Class<Component>) {
  // 接受传进来的选项:options
  Vue.prototype._init = function (options?: Object) {
    // vue的实例
    const vm: Component = this
    // vue实例标识符 _uid标志
    vm._uid = uid++
    // vue标志, 避免被 Observe 观察
    vm._isVue = true
    //处理组件的配置内容,将传入的options与构造函数本身的options进行合并(插件的策略都是默认配置和传入配置进行合并)
    if (options && options._isComponent) {
	// 处理1子组件:优化内部组件(子组件)实例化,且动态的options合并相当慢,这里只有需要处理一些特殊的参数属性。减少原型链的动态查找,提高执行效率
      initInternalComponent(vm, options)
    } else {
    	// 处理根组件: 将全局配置选项合并到根组件的配置上,其实就是一个选项合并
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }

    vm._self = vm
    initLifecycle(vm) // 初始化vue实例生命周期相关的属性,组件关系属性的初始化,定义了比如:$root、$parent、$children、$refs
    initEvents(vm) // 初始化自定义组件事件的监听,若存在父监听事件,则添加到该实例上
    initRender(vm) // 初始化render渲染所需的slots、渲染函数等。其实就两件事1、插槽的处理、2、$createElm 也就是 render 函数中的 h 的声明
    callHook(vm, 'beforeCreate')
    initInjections(vm) // 隔代传参时 先inject,作为一个组件,在要给后辈组件提供数据之前,需要先把祖辈传下来的数据注入进来
    initState(vm) // 对props,methods,data,computed,watch进行初始化,包括响应式的处理
    initProvide(vm) // 在把祖辈传下来的数据注入进来以后 再provide
    callHook(vm, 'created')
    // 接下来挂载
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

initState

  • src\core\instance\state.js
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  // 处理props对象,并把props对象的属性设置为响应式,且代理到vm实例上,可以用this的方式去获取
  if (opts.props) initProps(vm, opts.props)
  // 把methods对象的属性赋值到vm实例上,可以用this的方式去获取
  if (opts.methods) initMethods(vm, opts.methods)
  // data对象的属性设置为响应式,且把data对象的属性代理到vm实例上,可以用this的方式去获取
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 为每个计算属性(computed)创建一个内部观察者(watcher),把computed对象的属性代理到vm实例上,可以用this的方式去获取
  if (opts.computed) initComputed(vm, opts.computed)
  // 创建内部观察者watcher实例,也需要对watch对象的属性进行属性处理
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
  • 由上边可知,初始化的优先级为props > methods > data > computed > watch(面试题),这些都是在created前完成的。
  • 在此阶段,进行了大量的数据响应式处理,后边再做浅读。

$mount挂载

  • src/platforms/web/entry-runtime-with-compiler.js
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  //根据用户传入的 el 属性获取节点
  el = el && query(el)
  const options = this.$options
  // 根据不同情况解析模板及处理渲染函数
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
        }
      } 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) {
    //用 template 生成 render 函数
      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 方法开始渲染页面
  return mount.call(this, el, hydrating)
}
  • 由上边可知,vue的编译权重为:
    • 先看有没有render函数,如果有直接用
    • 如果没有render函数,再看有没有template模板
    • 如果都没有就直接获取el的outerHTML作为渲染模板
  • 如果用 template 进行编写HTML代码,vue 内部会把模板编译成 vue 可识别的 render 函数。
  • 如果有写 render 则可以省去编译过程。

  • src/core/instance/lifecycle.js
  • 简化一下
export function mountComponent (vm,elhydrating) {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  callHook(vm, 'beforeMount')
   //创建一个更新渲染函数 ( 用来得到 Vnode 渲染真实 dom )
  let updateComponent
  updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }
  //生成一个渲染 watcher 每次页面依赖的数据更新后会调用 updateComponent 进行渲染
  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
     //渲染真实 dom, 结束后调用 mounted 生命周期
    callHook(vm, 'mounted')
  }
  return vm
}
  • 这个过程创建了渲染 watcher ,渲染 watcher 内部调用了 updateComponent 方法
  • 关于watcher、虚拟dom与真实dom及diff后边再做浅读。

  • 至此。vue2的初始化流程大概就是这样,至于里边涉及很多细节,可循着初始化顺序依次看下去
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值