Vue原理剖析

源码目录剖析

  • comipler 模板解析的相关文件
  • core 核心代码
  • platforms 对应的两个平台
  • server服务端渲染相关
  • sfc 解析vue文件变成一个对象
  • shared 定义的工具方法

从入口文件剖析

引入代码时会优先采用module 如果找不到则会加载main对应文件

  • package.json

    • "main": "dist/vue.runtime.common.js",
    • "module": "dist/vue.runtime.esm.js"
  • 查看npm run build的结果

    • "build": "node scripts/build.js"
  • 查看build.js,build中会基于config文件来实现打包

    • 'web-runtime-cjs-dev' 只包含runtime
    • 'web-runtime-cjs-prod'
    • 'web-full-cjs-dev' 包含runtime+compiler
    • 'web-full-cjs-prod'
    • 'web-runtime-esm' 只包含runtime (es6 module)
    • 'web-full-esm' 包含runtime+compiler(es6 module)
    • .......

    找到打包前的文件 web/entry-runtime-with-compiler

  • entry-runtime-with-compiler

    • 找到对应platforms下的web下的entry-runtime-with-compiler文件
    • './runtime/index' Vue在runtime/index中定义
  • ./runtime/index

    • 引入了 'core/index'中的Vue
  • core/index

    • './instance/index' 引入了Vue
    • initGlobalAPI() 初始化全局API
  • instance/index

最终我们找到了Vue的构造函数

entry-runtime-with-compiler

在此文件中包装了$mount方法,如果有template会将template转化成render函数

  • compileToFunctions
    • 词法分析
    • 语法分析
    • 生成代码
    • new Function,转化成render函数

如果有render会先调用render,没有render会找模板是不是 #号方式引入.如果不是就找外部模板

runtime/index.js

  • Vue.prototype.patch 提供了__patach__方法
  • $mount方法通用的$mount方法,会调用挂载组件的方法

core/index.js

  • initGlobalApi 初始化全局api方法

initGlobalAPI

  • Vue.util 初始化vue的工具方法
  • Vue.set 响应式变化的方法
  • Vue.delete
  • Vue.nextTick
  • Vue.component
  • Vue.directive
  • Vue.filter

增加了keep-alive组件

Vue.nextTick // 浏览器事件环
initUse(Vue) // 初始化Vue.use 默认会调用当前插件的install方法
initMixin(Vue) //初始化Vue.mixin   混合 将传入的属性混合到this.options中
initExtend(Vue) // 初始化Vue.extend 会创建个子类 继承于父类
1
2
3
4

分析入口做了哪些事?

  • instance/index vue的构造函数中默认会调用this._init方法
// 初始化vue生命周期流程以及响应式流程启动
initMixin(Vue) // _init
// 初始实例属性和方法 $set\$delete\$watch 挂载与实例相关的方法
stateMixin(Vue)
// vue实例中事件相关的方法 $on\$once\$once\$off\$emit
eventsMixin(Vue)
// 生命周期相关方法  $forceUpdate $destroy
lifecycleMixin(Vue)
// 渲染函数 提供_render方法
renderMixin(Vue)
1
2
3
4
5
6
7
8
9
10

生命周期图

initMixin

initLifecycle(vm) // 初始化家族关系
initEvents(vm)    // 给实例增加events属性
initRender(vm)    // 在实例上增加createElement方法
callHook(vm, 'beforeCreate') // 调用beforeCreated方法
initInjections(vm) // 初始化inject方法
initState(vm)      // 初始化 props,method,data,watch
initProvide(vm)    // 初始化话provide方法
callHook(vm, 'created') // 调用created生命周期
vm.$mount(vm.$options.el) // 挂载元素  
1
2
3
4
5
6
7
8
9

initState

初始化实例上的内容

initProps(vm, opts.props) // 初始化属性
initMethods(vm, opts.methods) // 初始化方法
initData(vm)  // 初始化数据
initComputed(vm, opts.computed) // 初始化计算属性
initWatch(vm, opts.watch) // 初始化watch
1
2
3
4
5

iniProps

  • 将所有的属性放到_props上
  • 通过vm代理_props上的属性
  • 如果是跟节点将属性变成响应式的属性

initMethods

  • 在实例上增加对应的方法

initData

响应式原理

  • observe(data, true /* asRootData */)
  • this.walk(value) // 循环遍历
  • defineReactive(obj, keys[i]); // 定义响应式数据变化
  • get方法中dep.depend()依赖收集
  • set方法中dep.notify()通知更新

initComputed

  • 通过watcher实现计算属性
  • 如果watcher是脏的就获取最新结果

initWatch

  • 借用Watcher绑定$watch,初始化$watch方法

$mount

将模板编译成函数

compileToFunctions(template, {
    outputSourceRange: process.env.NODE_ENV !== 'production',
    shouldDecodeNewlines,
    shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments
}, this);
// 将解析出来的方法放到this上,调用上层mount方法
// mountComponent();
updateComponent = () => { // 增加更新组件方法
    vm._update(vm._render(), hydrating)
}
// 监听数据变化 执行beforeUpdate方法
new Watcher(vm, updateComponent, noop, {
    before () {
        if (vm._isMounted && !vm._isDestroyed) {
            callHook(vm, 'beforeUpdate')
        }
    }
}, true /* isRenderWatcher */)
callHook(vm, 'mounted') // 组件挂载完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

lifecycleMixin

调用_update方法

  • Vue.prototype._update
    • vm.patch() 渲染出真实的dom元素

__patch__方法

dom-diff原理

  • createPatchFunction

renderMixin

  • Vue.prototype._render 获取虚拟dom元素
  • vm._update会更新页面