Vue.js - 基礎原理

MichaelSonn 10年前發布 | 37K 次閱讀 Vue.js JavaScript開發

來自: 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</pre>

好吧,似乎 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</pre>

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

} }</pre>

PS

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

  • 請糾錯。

  • </ul> </div>

 本文由用戶 MichaelSonn 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!