vue源码系列2之代码结构及整体逻辑


目录结构

  • dist 打包后的vue版本
  • flow 类型检测,3.0换了typeScript
  • script 构建不同版本vue的相关配置
  • src 源码
    compiler 编译器
    core 不区分平台的核心代码
    global-api 全局API
    instance 实例的构造函数和原型方法
    observer 数据响应式
    util 常用的工具方法
    vdom 虚拟dom相关
  • platforms 不同平台不同实现 打包入口在这里
  • server 服务端渲染
  • sfc .vue单文件组件解析
  • shared 全局通用工具方法
  • test 测试

我们主要关注src
在这里插入图片描述

一、new vue 会发生什么

index.html

 debugger
        new Vue({
            el: '#root',
            data: {
                name: 233
            },
        })

进入断点调试,可以看到new Vue 进入了/src/core/instace/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)//进行一系列初始化并挂载
}
/*
*下面是初始化一些函数,比如_init等
*/
initMixin(Vue)//定义_init方法
stateMixin(Vue)//定义响应式方法 $set $delete $watch
eventsMixin(Vue)//定义事件的方法 $ ---on once off emit
lifecycleMixin(Vue)//定义生命周期的方法  _update $forceUpdate $destroy
renderMixin(Vue)//定义渲染相关的方法 $nextTick _render
export default Vue

Mixin:主要初始化方法,给Vue的原型挂载上这些方法。
注意:为啥不用ES6 class呢?
我认为:
1.兼容性
2.用函数的原型设计结构,不同功能不同文件,便于分离维护,这是vue的一个巧妙之处。

二、进入this._init(options)

src\core\instance\init.js
建议结合官网图:

在这里插入图片描述

1.结构(省略不必要)

注意有mark的是性能埋点,可以跳过的,而且是基于flow语法的,所以你的vscode 会爆红

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
    //略
    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      initInternalComponent(vm, options)//如果options 是一个内部组件 则内部组件实例化
    } else {
      vm.$options = mergeOptions(//合并参数,把用户传进来的参数和原型上的定义的默认参数合并
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
  
    // expose real self
    vm._self = vm
    initLifecycle(vm)//确认组件的父子关系和初始化某些实例属性。
    initEvents(vm)//初始化事件
    initRender(vm)//初始化渲染,挂载可以将render函数转为vnode的方法
    callHook(vm, 'beforeCreate')//执行
    initInjections(vm) // resolve injections before data/props 初始化注入
    initState(vm)//初始化数据,响应式数据
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)//挂载到dom
    }
  }

2. 进入entry-runtime-with-compiler.的this.$mount

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

代码结构:

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  //略
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
          template = idToTemplate(template)    
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      const { render, staticRenderFns } = compileToFunctions(template, {...}, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  return mount.call(this, el, hydrating)
}
  1. const mount = Vue.prototype.$mount
    这行是干啥的?
  1. vue有两个版本, 带编译的和不带编译器的,我们运行的vue.js 是带编译器的
  2. 如果是runtime only 是必须提供render 函数的,明显我们进入的entry-runtime-with-compiler.js,
  3. 这个文件的.$mount 作用是把el 或者template 生成render 函数,提供给runtime-only.js 的$mount()
  4. runtime-only.js 的Vue.prototype.$mount()在src\platforms\web\runtime\index.js
  1. 所以真正的挂载函数在src\platforms\web\runtime\index.js

  2. entry-runtime-with-compiler.js 的Vue.prototype.$mount 函数详解

  1. 获取el dom元素 (最终是替换dom 所以el 不能为body 或者html)
  2. template 优先级高于el ,如果有template 把template 字符串=>element,没有则用el代替
  3. compileToFunctions 把template=>render函数

3.进入runtime 的Vue.prototype.$mount()

作用是: render=>vdom=>dom 并收集订阅系统的依赖

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
    if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode
        ....
    }
    callHook(vm, 'beforeMount')

    let updateComponent
        /* istanbul ignore if */
    updateComponent = () => {
        vm._update(vm._render(), hydrating)
    }
    new Watcher(vm, updateComponent, noop, {
        before() {
            if (vm._isMounted && !vm._isDestroyed) {
                callHook(vm, 'beforeUpdate')
            }
        }
    }, true /* isRenderWatcher */ )
    if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
    }
    return vm
}
  1. 说明

也就是说beforeMount template/el 已经编译成render 函数,虚拟dom 还没生成
_render render()=>vdom,_update vdom => dom
updateComponent用函数包裹了_render(),_update然后new Watcher 收集updateComponent
当_render() 里面的用到了响应式数据,加入依赖收集,触发更新的时候再次执行updateComponent
这就是数据驱动试图。

到此基本逻辑已经完成,想看哪部分就debugger 进去那部分看

4.流程总结

new Vue(op)

  1. xxMixin先定义各种方法到Vue的原型上
  2. 进入_init
  3. 是options 是个组件则组件实例化,如果不是则合并参数
  4. 确认组件的父子关系,和事件,Render
  5. beforeCreate
  6. Injections ,响应式数据, Provide
  7. created
  8. compile-$mount template/el=>render
  9. runtime-$mount render =>vdom=>dom

总结

不要执着于细节,一步一步来。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值