本文将从官网的例子讲起,一步步对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) } } }
我希望SF支持ES6代码渲染。
请纠错。