Vue源码 new Vue()后都干了啥?

2,115 阅读3分钟

前言

本人毕业到现在工作一年,一直在开发React项目,但在毕业之前的实习基本上都在用vue开发,前一段时间工作比较轻松,又因为好久没有写vue项目,所以想再去熟悉一下vue, 但是痴情的我,居然想看vue的源码,于是我就去慕课网上买了一门源码讲解的课.前后看了两遍,可能是我太笨了,没有多久就忘记的差不多了,所以我决定再去看第三遍,并要做好记录.下面我就把我的记录和大家分享一下。

学前须知

如果想调试vue的源码,可以先用 vue create my-app 创建一个vue的项目,然后找到node_modules文件夹下的vue文件夹下的dist文件夹下的vue.esm.js或者vue.runtime.esm.js里进行调试

new Vue()后都干了些什么

我们看到上面的代码截图中用到了new Vue(),所以说源码里一定定义了一个名字叫 Vue() 构造函数,接下来我们就来看一下源码里是怎么定义这个函数的
我们看到这个 Vue() 函数就10行左右的代码,是不是很奇怪,我们先保持这个疑问, 向下继续看下面的几行代码是干什么的

initMixin(Vue);   // vue.prototype上添加 _init()方法
renderMixin(Vue);  //vue.prototype上添加 _render()方法
lifecycleMixin(Vue); //vue.prototype上添加 _update()方法

我们这一章主要讲这3行代码衍生出来的代码,让我们一起先进入initMixin(Vue)是干什么的。

initMixin(Vue)

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
     var vm = this; 创建vm, 
     ...
     // 合并options 到 vm.$options, 这里可以自己调试看一下vm.$options返回了什么
     vm.$options = mergeOptions(  
       resolveConstructorOptions(vm.constructor), 
       options || {},  
       vm 
     );
  }
  ...
   initLifecycle(vm); //初始生命周期
   initEvents(vm); //初始化事件
   initRender(vm); //初始render函数
   callHook(vm, 'beforeCreate'); //执行 beforeCreate生命周期钩子
  ...
   initState(vm);  //初始化data,props,methods computed,watch 
   ...
   callHook(vm, 'created');  //执行 created 生命周期钩子
   
   if (vm.$options.el) {
      vm.$mount(vm.$options.el); //这里也是重点,下面需要用到
   }
 }

从代码中我们来看一下initMixin() 都干了些什么.

  1. 第一步创建一个vm, (这个vm非常重要,一定要打个debugger调试以下,看看vm输出了什么)
  2. 第二步合并 vm.$options (这里可以自己调试看一下vm.$options返回了什么)
  3. 第三步 开始初始化 生命周期, 事件, render函数等等,
  4. 第四步 执行 vm.mount(vm.options.el);

这里我们先了解 initState(vm)是干什么的,都执行力什么函数

initState(vm)

function initState (vm) {
   ...
     var opts = vm.$options;
     if (opts.props) {  //如果你的.vue文件里定义了props,那么就会执行 initProps()
        initProps(vm, opts.props); 
     } 
     if (opts.methods) {  //如果你的.vue文件里定义了methods,那么就会执行 initMethods()
        initMethods(vm, opts.methods); 
     }
     if (opts.data) {//如果你的.vue文件里定义了data,那么就会执行 initData()
        initData(vm);
     } 
     if (opts.computed) { //如果你的.vue文件里定义了computed,那么就会执行 initComputed()
        initComputed(vm, opts.computed); 
     }
     if (opts.watch && opts.watch !== nativeWatch) {//如果你的.watch,那么就会执行 initWatch()
        initWatch(vm, opts.watch);
     }
}

我们到initState里的代码我们一定有些熟悉的感觉。 这里的代码再配上注释应该非常容易看懂. 由于代码很多,我就挑选了 initData(vm) 来详细讲解一下

function initData (vm) {
   var data = vm.$options.data;  //获取.vue文件里定义的data
       data = vm._data = typeof data === 'function'
       ? data.call(vm, vm) //如果data定义的是函数就把data的this指向vm
       : data || {}; 
       ...
       var keys = Object.keys(data);
       var props = vm.$options.props;
       var methods = vm.$options.methods;
      //遍历,data,props,methods,看是否定义相同的key,没有相同的name就执行,如果就控制台就会给出提示
       ...
       var i = keys.length;
         while (i--) {
           var key = keys[i];
           proxy(vm, "_data", key); 
        }
      //把data里的数据添加发布,订阅Object。defineProperty()
       observe(data, true /* asRootData */); 最后添加响应式,后面章节会详细讲解
}

function proxy (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
       return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter (val) {
       this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

从代码中我们来看一下initData() 都干了些什么.

  1. 第一步 获取data数据判断data是函数还是对象(这是个面试题,经常被问到)
  2. 第二步 获取data的key, props的key, methods的key, 看看是否有相同的 (如下图这样),如果没有相同的key,就遍历data里定义的数据,用Object.defineProperty()添加发布订阅, 如果不熟悉可以先去了解一下Object.defineProperty(), 这里用了_data, 在项目开发中用 this._data.msg 也可以获取到数据

这里我只讲解了initData()。 initProps也是大致的实现方法。

结束语

new vue() 就讲到这里, 下一次我们在继续向下了解,如果有人不清楚,可以在下方评论,可以向我获取视频