前言

本文是vue2.x源码分析的第二篇,主要讲解Vue初始化过程!

1、贯穿全文的例子:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Vue</title>
  6. <script src="./vue.js" type="text/javascript" charset="utf-8" ></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. {{message}}
  11. <div v-text='abc' v-on:click='methods_a'></div>
  12. </div>
  13. <script>
  14. var app=new Vue({
  15. el:'#app',
  16. name:'app',
  17. mixins:{
  18. beforeCreate(){
  19. console.log('beforeCreate-1');
  20. }
  21. }
  22. props:['header-value'],
  23. data:{
  24. message:'hello'
  25. },
  26. beforeCreate(){
  27. console.log('beforeCreate-2');
  28. },
  29. computed:{
  30. abc(){
  31. return this.message+'s'
  32. }
  33. },
  34. components:{
  35. ABF:'abf'
  36. },
  37. directives:{
  38. v-focus:function some(){}
  39. },
  40. extends:{
  41. ext_a:'a'
  42. },
  43. methods:{
  44. methods_a:function(){
  45. alert('methods_a')
  46. }
  47. },
  48. watch:{
  49. 'message':function(){
  50. console.log('message changed');
  51. },
  52. methods_a:function(){}
  53. }
  54. })
  55. // debugger;
  56. // setTimeout(()=>app.message='world',0)
  57. </script>
  58. </body>
  59. </html>

这里先放一个简单的例子,随着分析的深入,会一步步将例子变复杂。分析的手段就是通过设置断点一步步看执行过程。

2、new Vue({})执行过程分析

  • 首先找到Vue构造函数(启用IDE的查找功能能快速锁定)
  1. function Vue$3 (options) {
  2. if ("development" !== 'production' &&
  3. !(this instanceof Vue$3)) {
  4. warn('Vue is a constructor and should be called with the `new` keyword');
  5. }
  6. <!-- debugger; -->
  7. this._init(options); //程序主入口
  8. }
  • 打开上面代码的debugger注释来分析_init函数,该函数是通过initMixin(Vue)定义在Vue.prototype上的
  1. Vue.prototype._init = function (options) {
  2. var vm = this;
  3. // 每个vm实例都会有一个唯一的_uid属性
  4. vm._uid = uid++;
  5. // startTag, endTag 暂时不清楚干嘛的,不影响主流程,记为Unknown-2.1
  6. var startTag, endTag;
  7. /* istanbul ignore if */
  8. if ("development" !== 'production' && config.performance && mark) {
  9. startTag = "vue-perf-init:" + (vm._uid);
  10. endTag = "vue-perf-end:" + (vm._uid);
  11. mark(startTag);
  12. }
  13. // 设置_isVue标签避免被观测,记为Unknown-2.2
  14. vm._isVue = true;
  15. // 选项合并
  16. if (options && options._isComponent) {
  17. initInternalComponent(vm, options);
  18. } else {
  19. //主要执行过程-1(合并选项)
  20. vm.$options = mergeOptions(
  21. resolveConstructorOptions(vm.constructor),
  22. options || {},
  23. vm
  24. );
  25. }
  26. /* 设置代理 */
  27. {
  28. initProxy(vm);
  29. }
  30. // 将vm实例同时挂在_self属性上,记为Unknown-2.3
  31. vm._self = vm;
  32. // 主要执行过程-2(实例的初始化过程)
  33. initLifecycle(vm);
  34. initEvents(vm);
  35. initRender(vm);
  36. callHook(vm, 'beforeCreate');
  37. initInjections(vm); // resolve injections before data/props
  38. initState(vm);
  39. initProvide(vm); // resolve provide after data/props
  40. callHook(vm, 'created');
  41. /* istanbul ignore if */
  42. if ("development" !== 'production' && config.performance && mark) {
  43. vm._name = formatComponentName(vm, false);
  44. mark(endTag);
  45. measure(((vm._name) + " init"), startTag, endTag);
  46. }
  47. if (vm.$options.el) {
  48. // 主要执行过程-3(依赖收集、组件挂载等过程)
  49. vm.$mount(vm.$options.el);
  50. }
  51. };
  • 可以看到_init函数主要做了如下三件事,上述代码有标注

    • 主要执行过程-1(合并选项过程)
    • 主要执行过程-2(实例的初始化过程)
    • 主要执行过程-3(依赖收集、组件挂载等过程)
  • 这三件事后面会单独分析,除了这三件事,还有一个代理操作,来看看initProxy(vm)
  1. initProxy = function initProxy (vm) {
  2. //如果有本地Proxy类可用
  3. if (hasProxy) {
  4. // 判断用哪个代理处理对象
  5. var options = vm.$options;
  6. var handlers = options.render && options.render._withStripped
  7. ? getHandler
  8. : hasHandler;
  9. vm._renderProxy = new Proxy(vm, handlers);
  10. } else {
  11. vm._renderProxy = vm;
  12. }
  13. };

这里大家可能对Proxy类比较陌生,可以先看看这里了解基本用法。其实就是给vm实例做一个hasHandler代理,并将代理对象挂在_renderProxy上,使得在访问vm._renderProxy对象上的属性时先调用has方法,返回值为真才去取该属性。这在后面的_render函数会用到

3、小结

  • 下一步分析内容
    ☑ 主要执行过程-1(合并选项过程)
    ☐ 主要执行过程-2(实例的初始化过程)
    ☐ 主要执行过程-3(依赖收集、组件挂载等过程)
  • 遗留问题解决情况
    ☐ Unknown2.1
    ☐ Unknown2.2
    ☐ Unknown2.3