Vue.js - 基础原理

MichaelSonn 9年前

来自: http://segmentfault.com/a/1190000004221918

前言

本文将从官网的例子讲起,一步步对Vue.js的实现做讲解说明。

请注意下列事项:

  • 本文适合于使用了Vue.js一段时间,想进一步深入和对其实现原理有兴趣的人。

  • 本文基于 1.0.13 版本。

  • 本文较长,包含了部分vue.js源代码,删除了所有的警告信息(对本文来说没有任何的作用,而且影响阅读)和部分注释。

  • 文中对代码的注释做了部分翻译,以供阅读。

  • 作者是不写冒号主义者,可能会引起部分人的蛋疼。

  • 需要有ES6的知识作为基础。

  • 需要理解原型。

  • 文是我边看边写的。

  • 水平有限,如有错误请指出。

例子

下面是官网的例子,可以在上面直接看到执行的情况。接下来我们将会以这段代码做开头。

var demo = new Vue({    el: '#demo',    data: {      message: 'Hello Vue.js!'    }  })
<div id="demo">    <p>{{message}}</p>    <input v-model="message">  </div>

首先

我们可以从上面的代码得到一些信息。从js来看,我们是new了一个Vue实例,提供了一个el和一个data。el是为了和html做映射,data则是本身涵盖的数据。再看看html,id用于映射,{{message}}是数据的显示,input的值作为message的model。

这样html和js根据一个id做出了映射关系,并将data和html做了双向的关联,这就是典型的MVVM模式。即Modle、View和ViewModel。

现在

我们需要看看这之中的执行过程到底发生了什么。

对此,我们需要查看源代码。因为项目的组织是基于ES6的模块方式组织的,所以寻找和阅读并不是很困难。让我们先找到这个入口。

在 vue/src 文件夹里我们可以很容易的找到 index.js 文件,看起来这个就是入口。

import Vue from './instance/vue'  import directives from './directives/public/index'  import elementDirectives from './directives/element/index'  import filters from './filters/index'  import { inBrowser } from './util/index'    Vue.version = '1.0.13'    /**   * Vue and every constructor that extends Vue has an   * associated options object, which can be accessed during   * compilation steps as `this.constructor.options`.   *   * 每一个Vue实例都会有下列options   */    Vue.options = {    directives,    elementDirectives,    filters,    transitions: {},    components: {},    partials: {},    replace: true  }    export default Vue

好吧,似乎 vue/src/instance/vue.js 才是真正的本体。

// 构造函数  function Vue (options) {    this._init(options)  }    // install internals  initMixin(Vue)  stateMixin(Vue)  eventsMixin(Vue)  lifecycleMixin(Vue)  miscMixin(Vue)    // install APIs  globalAPI(Vue)  dataAPI(Vue)  domAPI(Vue)  eventsAPI(Vue)  lifecycleAPI(Vue)    export default Vue

我把import部分和部分注释删掉了,影响阅读。额,这个Vue构造函数里似乎只是执行了一个_init函数用来处理options,所以我们要接着找。

这个我们简单的全局搜一下就可以了,然后定位到 vue/src/instance/internal/init.js 。一看这个代码,我们就知道重点来了。

这个文件export了一个函数,看看 vue/src/instance/vue.js 我们就能知道其实就是initMixin这个函数。所以init就是在这个函数里被赋值的,我们直接看代码,这样会比较直观。

// mergeOptions这个函数,看名字是用来做options合并的  import { mergeOptions } from '../../util/index'    // uid?我们先不探讨  let uid = 0    // 被作为initMixin调用  export default function (Vue) {      // 这就是我们要找的东西 GJ    Vue.prototype._init = function (options) {        // 检查一下options是不是为空      options = options || {}        // 各种options,这里是各个默认值      this.$el = null      this.$parent = options.parent      this.$root = this.$parent        ? this.$parent.$root        : this      this.$children = []      this.$refs = {}       // child vm references      this.$els = {}        // element references      this._watchers = []   // all watchers as an array      this._directives = [] // all directives        // 哦,是Vue的实例个数      this._uid = uid++        // a flag to avoid this being observed      this._isVue = true        // events bookkeeping      this._events = {}            // registered callbacks      this._eventsCount = {}       // for $broadcast optimization        // fragment instance properties      this._isFragment = false      this._fragment =         // @type {DocumentFragment}      this._fragmentStart =    // @type {Text|Comment}      this._fragmentEnd = null // @type {Text|Comment}        // lifecycle state      this._isCompiled =      this._isDestroyed =      this._isReady =      this._isAttached =      this._isBeingDestroyed = false      this._unlinkFn = null        // context:      // if this is a transcluded component, context      // will be the common parent vm of this instance      // and its host.      this._context = options._context || this.$parent        // scope:      // if this is inside an inline v-for, the scope      // will be the intermediate scope created for this      // repeat fragment. this is used for linking props      // and container directives.      this._scope = options._scope        // fragment:      // if this instance is compiled inside a Fragment, it      // needs to reigster itself as a child of that fragment      // for attach/detach to work properly.      this._frag = options._frag      if (this._frag) {        this._frag.children.push(this)      }        // push self into parent / transclusion host      if (this.$parent) {        this.$parent.$children.push(this)      }        // merge options.      options = this.$options = mergeOptions(        this.constructor.options,        options,        this      )        // set ref      this._updateRef()        // initialize data as empty object.      // it will be filled up in _initScope().      this._data = {}        // call init hook      this._callHook('init')        // initialize data observation and scope inheritance.      this._initState()        // setup event system and option events.      this._initEvents()        // call created hook      this._callHook('created')        // if `el` option is passed, start compilation.      if (options.el) {        this.$mount(options.el)      }    }  }

PS

  • 我希望SF支持ES6代码渲染。

  • 请纠错。