Vue 源码(二):Vue

首先,我们仍然和前面随手写的 Vue 一样,需要先创建一个闭包,然后给 window 上面扩展一个 Vue 的接口,然后我们再定义一个 Vue 的构造函数,最后在 return 出去。

我们可以看到,在源码中,它这里就写的相对复杂一点。因为上面代码中的 exports 和 module 它们本身并不是我们客户端的对象,而是我们服务端 node.js 里面的2个对象,也就是说遵循了 CommonJS 规范的话,就会有这2个对象。

如果说我们的运行环境是在遵循了 CommonJS 的规范下,那我们就直接调用传过去的函数 factory,然后将它的返回值作为我们的接口对象。

当然在整个的市场环境下面,我们更多的是去兼容 RequireJs 的规范,如果说有 define 这个函数,并且 define 有 amd 这个属性,那我们就直接的去定义当前的这个模块,然后在把 factory 给它传过去,它里面的返回值就作为我们的接口对象。

那如果我们既没有 module 也没有 define,那我们就直接去调用 factory,然后把它的返回值作为 global.Vue 上面我们要扩展的值。

Vue 源码中的是三目写法,也就是简写,还原之后,就是这样的:

相信这样看,就更直观和容易看懂些了。然后,我们继续:

根据 new Vue(),我们会接收一个 options 的选项,但是我们在接收这个选项的时候,在 Vue 里面会做一个安全模式的提示。

也就是说我们在使用 Vue 的时候,必须要通过 new Vue() 来创建Vue的实例,而不是说直接去这样调用 Vue 这个构造函数,比如 Vue()。

如果我们切换到 Vue 源码,并使用 Vue(),就可以看到它给我们一个警告,Vue 不能这样使用,它必须要通过 new 这个关键字来创建实例,而且还会报另外一个错,当前的 this 下面,是没有 _init 这个方法的。

所以,也就意味着,在源码里面,它会有一个安全机制,来确定我们在执行 Vue 这个构造函数的时候,是把它当做构造函数来使用,而不是把它当做一个普通的函数来执行。

并且,第二个报错也说明了接下来它会调用 this._init(),也就是说我们会做初始化操作。那么我们直接使用 Vue(),它里面的 this 就是 window,所以它就会报错,因为 _init 这个方法是给 Vue 的实例来拓展的。所以我们才会有安全机制这个东西,那么它怎么做的呢?

在 Vue 整个框架里面,我们有很多的一些错误的调用,错误的配置,我们都需要使用到 console.error(...) 来给用户一个提示信息,那么我们就可以把这句话单独的拿出来当做一个方法,比如:

然后,在整个Vue里面,我们所有的一些实例上的方法,它并不是我们直接的写在原型上面,比如就和我们上一篇博客随手写的 Vue 一样,我们把所有的一些方法都是通过 Vue.prototype.xxx 这样写的。

然而实际上,在Vue里面,一开始的时候,它就会做一些一系列的初始化的操作,在这些初始化的操作的过程中,如果说我们需要用到哪些方法,我们就会在初始化这个方法里面给它进行定义,它是这样子来设计的,如下图:

_init 方法里面 this 就是指 Vue 的实例,我们将它赋值给 vm。同时,我们会给 Vue 的实例拓展一个 _uid 的属性,拓展它作用的是用来统计你创建了多少个 Vue 的实例。因为后面我们还要做一些性能的监控,所以我们在这里就需要有这个动作。

接下来,我们会做一些选项的合并,那么我们应该怎么合并,合并哪些东西呢?

我们在 Vue 开发中,会有很多的参数,选项,它们本身是由 Vue.options 来提供的,但是还有一些参数和选项是我们自己在创建 Vue 实例的时候,给它传过去的,比如说 el, data 等等。

我们可以直接引用源码,打印 vm.$options,然后在控制台上直接看到:

我们可以看到,有很多属性是我们自己在配置的时候没有传的。说明 Vue 里面它还有很多一些它内置的一些成员。

那么还有一个有趣的事情就是,如果我们自定义了一些选项,比如我们添加一个 name: '大木':

我们就可以看到,这个自定义的属性,它也会合并到 Vue 的选项里面。

那么到这里,我们就明确了一些东西,实际上我们在使用 Vue 的过程中,我们可以给它传一个对象 options,这个对象里面,它有规定我们要传入哪一些参数,这些参数和选项它有规定好,都是什么样的含义,但是我们也可以给它自定义一些参数和选项。

于此同时,Vue 本身它也会有一个 options 的属性,这个属性它所对应的也是一个对象。在这个对象里面,它也会有我们当前整个 Vue 一些初始化的属性,组件,过滤器,等等功能。

但是,如果你有了,你就会把它给覆盖掉,比如 components 它默认就是一个空对象,如果我们给它拓展一个 components 的属性,并加入 componentA 这个组件,那么我们就可以在控制台上面看到,它多了一个 component-a 这个组件,就是我们传入的配置:

所以,我们在这里就会做一个合并,把 Vue.options 它初始化的功能和我们传过去的 options 对象来进行合并。

如果说当我们遇到了一些相同的属性的时候,那么我们就可以把 Vue.options 它里面的值给覆盖掉,最终在给到 Vue 的实例 vm。

那么我们怎么来合并呢?我们可以通过 mergeOptions 这个方法来做选项的合并。然后我们合并的是谁?首先第一个要合并的是Vue.options,第二个是我们自己本身所传过去的 options,第三个要把 vm 实例给传过去。

然而我们发现,Vue.options 我们现在并没有定义,所以我们需要通过一个方法 resolveConstructorOptions,来解析构造函数它的参数,我们就可以把它的返回值作为我们的参数给它传过来。

那么我们在调用它的时候,我们会把 vm.constructor 给它传过去。那么接下来,我们就先定义 resolveConstructorOptions 这个方法。

在它里面,Con 指的就是Vue这个构造函数,那么它一定就指向 Vue 构造函数吗?

其实,在有一些情况下面,它指向的并不总是 Vue。如果说我们在调用 resolveConstructorOptions 的时候,我们是作为 Vue 的一个子类来调用的,那么这个时候 Con 的指向就会发生改变,从而指向它的子类。

就比如 Vue 里面有个方法叫做 extend,我们可以通过调用它来创建一个 Vue 的子类,当我们创建子类的时候,实际上也会执行 resolveConstructorOptions 这个方法,那么 Con 指向的就是这个子类本身。所以说,我们在这里需要来做一层判断。

然后我们来给 Vue 拓展一个 options 的属性,那么它定义的是什么?它定义的就是一些 Vue 的全局的 API。我们仍然可以通过引用源码然后打印,可以看到:

我们可以先只写三个,给 Vue.options 里面设置了一些全局 API 的三个功能,组件components,指令directives,_base。

_base 所指的就是 Vue 本身,那么为什么要有 _base 的存在呢?因为很多时候,如果我们想要给 Vue 来拓展一些方法的话,那么我们就可以通过 Vue.options._base 这种方式来进行拓展。

发布了61 篇原创文章 · 获赞 3 · 访问量 4389

猜你喜欢

转载自blog.csdn.net/weixin_43921436/article/details/100005784