Vue.js - 基础原理

Vue.js   2016-01-31 15:59:46 发布
您的评价:
     
0.0
收藏     0收藏
文件夹
标签
(多个标签用逗号分隔)

来自: 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代码渲染。

  • 请纠错。

扩展阅读

Vue.js的日历组 :vue-calendar
Vue.js + webpack 项目实践
Vue.js 快速入门
Vue.js 源码学习笔记
Vue.js基本语法的介绍

为您推荐

jquery实现网站向导提示操作插件
图片滚动jQuery插件 myScroll
AngularJS学习笔记
jQuery常用方法
jQuery常用方法一览

更多

Vue.js
JavaScript开发