從vue的構建過程能夠知道,web環境下,入口文件在 src/platforms/web/entry-runtime-with-compiler.js
(以Runtime + Compiler模式構建,vue直接運行在瀏覽器進行編譯工做)html
import Vue from './runtime/index'
下一步,找到./runtime/index
,發現:vue
import Vue from 'core/index'
下一步,找到core/index
,發現:java
import Vue from './instance/index'
按照這個思路找,最後發現:Vue是在'core/index'下定義的web
import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index' 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) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
引入方法,用function
定義了Vue類
,再以Vue
爲參數,調用了5個方法,最後導出了vue
。瀏覽器
能夠進入這5個文件查看相關方法,主要就是在Vue
原型上掛載方法,能夠看到,Vue
是把這5個方法按功能放入不一樣的模塊中,這很利於代碼的維護和管理緩存
回到core/index.js
, 看到除了引入已經在原型上掛載方法後的 Vue
外,還導入initGlobalAPI 、 isServerRendering、FunctionalRenderContext
,執行initGlobalAPI(Vue)
,在vue.prototype
上掛載$isServer、$ssrContext、FunctionalRenderContext
,在vue
上掛載 version
屬性,app
看到initGlobalAPI
的定義,主要是往vue.config、vue.util
等上掛載全局靜態屬性和靜態方法(可直接經過Vue調用,而不是實例調用),再把builtInComponents 內置組件
擴展到Vue.options.components
下。此處大體瞭解下它是作什麼的便可,後面用到再作具體分析。dom
new Vue()
通常咱們用vue
都採用模板語法來聲明:函數
<div id="app"> {{ message }} </div>
var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
當new Vue()
時,vue
作了哪些處理?oop
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) }
看到vue
只能經過new
實例化,不然報錯。實例化vue
後,執行了this._init()
,該方法在經過initMixin(Vue)
掛載在Vue
原型上的,找到定義文件core/instance/init.js
查看該方法。
_init()
一開始在this
對象上定義_uid、_isVue
,判斷options._isComponent
,這次先不考慮options._isComponent
爲true
的狀況,走else
,合併options
,接着安裝proxy
, 初始化生命週期,初始化事件、初始化渲染、初始化data、鉤子函數等,最後判斷有vm.$options.el
則執行vm.$mount()
,便是把el
渲染成最終的DOM
。
data
數據綁定_init()
中經過initState()
來綁定數據到vm上,看下initState
的定義:
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
獲取options
,初始化props
、methods
、data
、計算屬性、watch
綁定到vm
上,先來看下initData()
是如何把綁定data
的:
data
是否是function
類型,是則調用getData
,返回data的自調用,不是則直接返回data
,並將data
賦值到vm._data
上data、props、methods
,做個校驗,防止出現重複的key
,由於它們最終都會掛載到vm
上,都是經過vm.key
來調用經過proxy(vm, `_data`, key)
把每一個key
都掛載在vm
上
export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) } const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop }
proxy()
定義了一個get/set
函數,再經過Object.defineProperty
定義修改屬性(不瞭解Object.defineProperty()
的同窗能夠先看下文檔,經過Object.defineProperty()
定義的屬性,經過描述符的設置能夠進行更精準的控制對象屬性),將對target的key訪問加了一層get/set
,即當訪問vm.key
時,其實是調用了sharedPropertyDefinition.get
,返回this._data.key
,這樣就實現了經過vm.key
來調用vm._data
上的屬性
observe(data, true /* asRootData */)
觀察者,對數據做響應式處理,這也是vue
的核心之一,此處先不分析$mount()
實例掛載Vue
的核心思想之一是數據驅動,在vue
下,咱們不會直接操做DOM
,而是經過js修改數據,全部邏輯只須要考慮對數據的修改,最後再把數據渲染成DOM。其中,$mount()
就是負責把數據掛載到vm
,再渲染成最終DOM
。
接下來將會分析下 vue
是如何把javaScript對象渲染成dom
元素的,和以前同樣,主要分析主線代碼
仍是從src/platform/web/entry-runtime-with-compiler.js
文件入手,
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) ··· }
首先將原先原型上的$mount
方法緩存起來,再從新定義$mount
:
el
,el
不能是 body, html
,由於渲染出來的 DOM
最後是會替換掉el
的render
方法, 有的話直接調用mount.call(this, el, hydrating)
render
方法時:template
,有則用compileToFunctions
將其編譯成render方法template
時,則查看有沒有el
,有轉換成template
,再用compileToFunctions
將其編譯成render
方法render
掛載到options下mount.call(this, el, hydrating)
,便是調用原先原型上的mount方法咱們發現這一系列調用都是爲了生成render
函數,說明在vue
中,全部的組件渲染最終都須要render
方法(無論是單文件.vue仍是el/template
),vue
文檔裏也提到:
Vue
選項中的 render 函數若存在,則Vue
構造函數不會從template
選項或經過 el 選項指定的掛載元素中提取出的HTML
模板編譯渲染函數。
mount
方法找到原先原型上的mount
方法,在src/platform/web/runtime/index.js
中:
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
這個是公用的$mount
方法,這麼設計使得這個方法能夠被 runtime only
和runtime+compiler
版本共同使用
$mount
第一個參數el
, 表示掛載的元素,在瀏覽器環境會經過query(el)
獲取到dom
對象,第二個參數和服務端渲染相關,不進行深刻分析,此處不傳。接着調用mountComponent()
看下query()
,比較簡單,當el
是string
時,找到該選擇器返回dom
對象,不然新建立個div
dom對象,el
是dom
對象直接返回el
.
mountComponent
定義在src/core/instance/lifecycle.js
中,傳入vm,el
,
el
緩存在vm.$el
上render
方法,沒有則直接把createEmptyVNode
做爲render
函數Render
但有el/template
不能使用runtime-only
版本、render
和template
必需要有一個)beforeMount
鉤子定義 updateComponent
, 渲染相關
updateComponent = () => { vm._update(vm._render(), hydrating) }
new Watcher()
實例化一個渲染watcher,簡單看下定義, this.getter = expOrFn
把updateComponent
掛載到this.getter
上this.value = this.lazy ? undefined : this.get()
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) {...} return value }
執行this.get()
,則執行了this.getter
,即updateComponent
,因此new Watcher()
時會執行updateComponent
,也就會執行到vm._update、vm._render
方法。
由於以後不止初始化時須要渲染頁面,數據發生變化時也是要更新到dom上的,實例watcher能夠實現對數據進行監聽以及隨後的更新dom
處理,watcher
會在初始化執行回調,也會在數據變化時執行回調,此處先簡單介紹爲何要使用watcher
,不深刻分析watcher
實現原理。
mounted
鉤子函數 ,返回vm實例初始化:new Vue()->掛載方法屬性->this._init->初始化data->$mount
掛載過程:(在complier
版本,生成render
函數)對el做處理,執行mountComponent
,mountComponent
中定義了updateComponent
,經過實例化watcher
的回調執行updateComponent
,執行updateComponent
,即調用了vm._update、vm._render
真實渲染成dom
對象。