Vue源码解析

本文仅对Vue主体做一个流程化的简单解析,其他详细功能的代码分析过程是相同的。阅读源码之前,首先要确保对Vue.js的使用较为熟悉。先知道怎么用,再研究怎么实现。

 

首先去github下载官方源码:

https://github.com/vuejs/vue

当前版本为2.5.17。

解压缩。为了方便查看,导入到webstrom中:

源码必然在src文件夹。

查看src文件夹:

从文件夹名看,compiler应该是编译相关;core应该是内核相关;platforms应该是平台相关;server应该是服务端相关;sfc无法直观猜测;shared是共享的意思,从名称也无法直接猜测出作用。要查看vue的主体实现,那这几个文件夹,必然应该选core。

打开core,列出了几个文件夹,以及两个js文件:config.js和index.js。

config.js很明显是配置文件。index.js,这必然就是入口了。于是打开index.js:

可以看到做了几个事情:

1.    import了一些对象进来。

2.    调用了initGlobalAPI()。

3.    调用了Object.defineProperty()。

4.    为Vue对象设置version变量。

5.    将Vue对象默认导出。也就是说,虽然Vue是导入的,但经过一系列操作后,又将Vue进行了导出。相当于是Vue对象在该文件中进行了加工。

imoprt进来的对象,必然是后面用到的。所以可以先不管,等用到的时候再回来查。先来看执行的第一个函数:

initGlobalAPI(Vue)

面对这行代码,需要搞清楚两个问题:

①   initGlobalAPI()是在哪定义的,具体实现是什么。

②   参数Vue明显是个对象,且跟Vue库同名,所以Vue必然是最重要的主对象。这个Vue是在哪定义的,具体实现是什么。

这两个问题,明显②比较重要。所以先看Vue的实现。

查看最上方的import,有这么一行:

import Vue from './instance/index'

也就是说,Vue是在与index.js同目录下的instance文件夹下的index.js中定义的。

打开该index.js:

可以看到在这个index.js中做了几个事情:

1.    import了一些对象进来。

2.    定义了一个名叫Vue的function,且传入了一个options参数

3.    调用了许多函数,且都将Vue作为参数传入。

4.    将Vue默认导出。

同样不管import,先来分析Vue这个function的定义。

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

这个function的几个要点:

①   名称是Vue,js常用的扩展方式是实现一个function,然后再new。这个函数就是这样的一个构造函数。回想我们在用Vue的时候,也都是采用

var vm = new Vue({
  // 选项
})

这样的形式。

②   函数内首先是一个if。判断条件暂且不看,if若成立,会有一个warn:

warn('Vue is a constructor and should be called with the `new` keyword')

大意是,该Vue是一个构造函数,必须使用new来调用。

于是就可以猜测if的判断条件,必然是:若不使用new来调用了Vue,则提示warn。

③   函数最后是一个函数调用:

this._init(options)

this就是new出的Vue对象了。_init()必然是Vue的一个成员函数,从函数名看,其作用是初始化。options是我们传入的参数。结合Vue的具体使用,可知传入的options就是那一堆data,props,methods等。

那么,现在的一个疑问就是,Vue对象是在什么时候定义了这个_init()成员函数?

继续往下看代码,此时执行了5个函数:

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

根据import,可以找到这5个函数的js文件。先来看第一个:

import { initMixin } from './init'

打开同级目录下的init.js,可以看到:

首先imoprt了一堆对象进来。

然后定义了一个uid,并赋值为0。

然后导出了一个function,名为initMixin(),接受一个class类型参数,参数名为Vue。

进入initMixin(),第一行代码就是:

Vue.prototype._init = function (options?: Object) {

这里直接修改了传入参数Vue的prototype,为其设置了一个名为_init的函数,该函数接收一个Object类型参数,参数名为options。

于是上面的疑问得以解答:Vue对象的_init()成员函数是在initMixin()中创建并挂载到Vue对象上的,并且对Vue对象而言是个prototype全局函数。

然后继续来看_init()都做了些什么工作:

Vue.prototype._init = function (options?: Object) {
  const vm: Component = this
  // a uid
  vm._uid = uid++

  let startTag, endTag
  /* istanbul ignore if */
 
if (process.env.NODE_ENV !== ‘production’ && config.performance && mark) {
    startTag = vue-perf-start:</span></strong>${<span style="color:#458383;">vm</span>._uid}<strong><span style="color:#008000;">
   
endTag = vue-perf-end:</span></strong>${<span style="color:#458383;">vm</span>._uid}<strong><span style="color:#008000;">
   
mark(startTag)
  }

  // a flag to avoid this being observed
 
vm._isVue = true
 
// merge options
 
if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent
(vm, options)
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )
  }
  /* istanbul ignore else */
 
if (process.env.NODE_ENV !== ‘production’) {
    initProxy(vm)
  } else {
    vm._renderProxy = vm
  }
  // 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’)

  /* istanbul ignore if */
 
if (process.env.NODE_ENV !== ‘production’ && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(vue </span></strong>${<span style="color:#458383;">vm</span>._name}<strong><span style="color:#008000;"> init, startTag, endTag)
  }

  if (vm.KaTeX parse error: Expected '}', got '&' at position 15: options.el) { &̲nbsp;&nbsp;&nbs…mount(vm.KaTeX parse error: Expected 'EOF', got '&' at position 13: options.el) &̲nbsp; } }</pre>…options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
}

最上方的注释意思是合并options,所以下面的if…else主要作用的合并传入的options。

先看if条件。当options存在并且options._isComponent为true时,就会调用initInternalComponent(vm, options)。

_isComponent从变量名看,其含义应该是一个标记,用来记录当前options是否属于组件。而initInternalComponent()从函数名看,其作用应当是初始化内部组件。

所以推断,当options属于一个组件时,就会进入if,对内部组件进行初始化。

而对于else,将传入的options与vm本身的属性进行了合并,并重新赋值给vm.KaTeX parse error: Expected 'EOF', got '#' at position 91: …le="background:#̲FFFFFF;"><em><s…options

  // locate first non-abstract parent
 
let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.KaTeX parse error: Expected 'EOF', got '#' at position 43: …n style="color:#̲660E7A;">abstra…parent) {
      parent = parent.KaTeX parse error: Expected 'EOF', got '&' at position 11: parent<br>&̲nbsp;&nbsp;&nbs…children.push(vm)
  }

  vm.KaTeX parse error: Expected 'EOF', got '#' at position 36: …n style="color:#̲458383;">parent…root = parent ? parent.KaTeX parse error: Expected 'EOF', got '&' at position 18: …ot : vm<br><br>&̲nbsp; vm.children = []
  vm.KaTeX parse error: Expected 'EOF', got '&' at position 18: …fs = {}<br><br>&̲nbsp; vm._watch…children。

②   将vm的parent修改为①中找到的parent。

③   修改vm的根 r o o t 。 若 v m 的 p a r e n t 存 在 , 则 与 p a r e n t 的 root。若vm的parent存在,则与parent的 rootvmparentparentroot统一;否则KaTeX parse error: Expected 'EOF', got '&' at position 20: …就是vm自身。</p><p>④&̲nbsp;&nbsp; 修改v…options.el) {
  vm.KaTeX parse error: Expected 'EOF', got '#' at position 33: …n style="color:#̲458383;">vm</sp…options.el)
}

当el存在时,将其挂载到vm实例上。

这样,initMixin()就执行完成了。综上可知,该函数为vm添加了各种变量与函数,就像函数名一样:init是初始化的意思,mixin是混入的意思,initMixin()的主要作用就是将vm所需的各种初始化变量与函数混入到vm对象中。

然后返回到instance/index.js中来。

同理:

stateMixin(Vue) :混入props,methods,data,computed,watch。
eventsMixin(Vue) :混入_events,并更新组件的listeners。
lifecycleMixin(Vue) :混入_vnode, e l , el, elparent相关。
renderMixin(Vue) :混入渲染相关的内容,如$slots等。

经过这些混入操作,Vue由一个什么属性都没有的空对象,变为拥有一堆变量与函数的丰满对象。

现在回到core/index.js。

上面已经解决了最初的两个问题中的第②个,即Vue对象是在哪里定义的,具体实现是什么。

然后来看问题①:initGlobalAPI()是在哪定义的,具体实现是什么。

同理,根据imoprt,打开core/global-api/index.js,查看initGlobalAPI()的具体实现,会发现该函数对Vue对象进行了以下几个操作:

①   为Vue实例设置了一个空的config属性。

②   为Vue的util设置了几个成员,包含 :warn,extend,mergeOptions,defineReactive。

③   为Vue设置了set,delete,nextTick,以及一个空的options。其中options按类型填充默认空对象。

④   将自身赋给options._base。

⑤   将options.components与builtInComponents合并。

⑥   初始化use,mixin,extend,assetRegisters功能。

于是,经过initGlobalAPI(),Vue实例增加了更多的变量和功能,主要是全局化相关的内容。

现在回到core/index.js。

经过三个属性的设置,以及version修改,最后将功能非常完善的Vue实例对象默认返回。

这个Vue本质上依然是一个构造函数。于是,我们在前端导入Vue.js后,调用:

var vm = new Vue({
  // 选项
})

得到的vm对象就是一个包含了所有功能的Vue实例。

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值