Vue2.X源码学习第一天

246 阅读3分钟

Vue2.X源码学习第一天

学习目标

学习vue的工作原理,编译以及初始化流程

修改配置

为了方便debug,首先要对配置文件进行修改,package.json文件中scrpts添加

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"

其次,在examples文件中,复制一个html文件,将vue打包完成后的引入文件改为<script src="../../dist/vue.js"></script>

修改完成,打开页面,在f12中的source中就可以看到与源码对应的文件目录结构,

随后运行代码,直接修改代码,刷新页面就可以直接看到修改后的效果,方便debug


入口文件

所谓擒贼先擒王,找到入口文件是学习源码的第一步。因为调试使用的umd格式的代码,则入口文件为

src\platforms\web\entry-runtime-with-compiler.js,然后进入文件,进行追踪Vue的来源。

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

  • 文件主要功能:扩展$mount方法,解析option的render template el的处理方法

  • Vue来源:src\platforms\web\runtime\index.js

2.src\platforms\web\runtime\index.js

  • 文件主要功能:声明patch函数,实现$mount:执行挂载函数mountComponent

  • Vue来源:src\core\index.js

3.src\core\index.js

  • 文件主要功能:执行initGlobalAPI进行公共方法挂载

  • Vue来源:src\core\instance\index.js

4.src\core\instance\index.js

  • 文件主要功能:Vue构造源码位置

这样,我们就找到了Vue构造函数的所在地在src\core\instance\index.js中,

既然 找到了构造函数,那么可以分两步进行探索

  1. Vue的初始化流程
  2. new Vue的初始化流程

为了更容易的学习Vue源码,先从new Vue的初始化流程进行学习


new Vue 流程

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')

 }

 this._init(options)//初始化执行的主要方法

}

接下来跟上面追踪入口文件一样,进行new Vue 流程的追踪

构造函数中主要执行方法为this._init(options),全局搜索_init方法,定位其位置在 src\core\instance\init.js文件中

Vue.prototype._init方法,位置src\core\instance\init.js

Vue.prototype._init = function (options?: Object) {
  //-----------------------------省略-------------------------------

  // expose real self

  vm._self = vm

  initLifecycle(vm)

  initEvents(vm)

  initRender(vm)

  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)

  }

 }

省略部分代码,将核心代码暴露出来,第一眼可以看到的就是callHook(vm, 'beforeCreate')callHook(vm, 'created')

没错,最熟悉的钩子函数出场了,中间夹杂了很多其他的方法,这就是为什么执行beforeCreatecreated方法时候,this内部结构不一致,后者比前者多,就是因为两者之间还有其他方法执行,对实例进行操作。

接下来就对这些方法进行总结

initLifecycle

对实例属性的parent,parent,root,$children等属性进行赋值

initEvents

对实例属性事件进行监听

initRender

对实例属性slots,slots,createElement进行赋值

initInjections

对祖辈传递的数据进行赋值

initState

对象实例属性初始化methods,data,props,computed等

initProvide

$options里的provide赋值到当前实例上

总结_init方法

new Vue流程就是执行了上述代码后完成了对实例的创建,在代码的最后,执行了vm.$mount(vm.$options.el)方法,进行vue对象的下一步操作,接下来就继续追踪$mount方法


$mount

在找Vue入口文件的时候追踪的文件中,src\platforms\web\runtime\index.js文件中对$mount进行了赋值操作

Vue.prototype.$mount = function (

 el?: string | Element,

 hydrating?: boolean

): Component {

 el = el && inBrowser ? query(el) : undefined

 return mountComponent(this, el, hydrating)//主要代码

}

$mount主要执行了mountComponent方法,继续追踪

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 */

 if (process.env.NODE_ENV !== 'production' && config.performance && mark) {

  //...

 } else {

  updateComponent = () => {

   vm._update(vm._render(), hydrating)

  }

 }


 new Watcher(vm, updateComponent, noop, {

  before () {

   if (vm._isMounted && !vm._isDestroyed) {

?    callHook(vm, 'beforeUpdate')

   }

  }

 }, true /* isRenderWatcher */)

 hydrating = false



 // manually mounted instance, call mounted on self

 // mounted is called for render-created child components in its inserted hook

 if (vm.$vnode == null) {

  vm._isMounted = true

  callHook(vm, 'mounted')

 }

 return vm

}

这一块代码看着头都大,不过还是要头铁进行硬看,还是第一眼印象中,看到了两个熟悉的代码callHook(vm, 'beforeMount')callHook(vm, 'mounted'),钩子函数还是看得懂的。这个方法看起来很长,但是实际的功能比较少。

1。对实例属性$el进行了挂载

vm.$el = el

2.然后创建了一个updateComponent的声明,
updateComponent = () => {

   vm._update(vm._render(), hydrating)

}
3.创建Watcher,将updateComponent交给 Watcher进行管理
new Watcher(vm, updateComponent, noop, {

  before () {

   if (vm._isMounted && !vm._isDestroyed) {

?    callHook(vm, 'beforeUpdate')

   }

  }

 }, true /* isRenderWatcher */)
总结$mount

对实例进行收尾处理,render渲染。

总结

第一天学习,先学大概流程,后面再慢慢看细节。中间如果对某个方法感兴趣可以直接运行代码debug,可以更加直观的了解vue源码。

好好学习,升职加薪迎娶白富美不是梦。