0.獲取源碼 https://github.com/vuejs/vue 從github地址,直接download下來就行了。在新建項目的時候也可以node_modelus里的vue搭配著看。 1.數據的掛載 首先先引入vue,然後新建他的實例。 首先我們得知道我們引入 的是個什麼東西。所以我們找到源 ...
0.獲取源碼
從github地址,直接download下來就行了。在新建項目的時候也可以node_modelus里的vue搭配著看。
1.數據的掛載
首先先引入vue,然後新建他的實例。
import Vue from 'vue'
var app = new Vue({
el:'#app',
data:{
return {
message:"hello world!"
}
}
})
首先我們得知道我們引入 的是個什麼東西。所以我們找到源碼./src/core/instance/index.js
里,找到了vue的廬山真面目了,其實vue就是一個類。
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)
}
首先process.env.NODE_ENV
是判斷你啟動時候的參數的,如果不符合的話,就發出警告,否則執行_init
方法。值得一提的是一般屬性名前面加_
預設代表是私有屬性,不對外展示。當然如果你列印vue實例的話還是能看見,因為只是_
是私有屬性人們約定俗成的,沒有js語言層面的私有。
那麼這個_init
是哪來的呢?往下看:
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
可以看到下麵有一大串Mixin,我們挑第一個initMixin,然後去查看他的定義。vscode可以直接右鍵,然後選擇轉到定義 或者直接command加滑鼠左鍵點擊函數名稱就可以跳過去看到定義這個方法的地方。
export function initMixin(Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
//..
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
//..
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
// ..
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
init就在最開頭
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
//..
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
init具體包括啥呢,首先將this上下文傳給vm這個對象,然後設置_uid
然後再機型一系列的初始化的工作。然後再合併options,最後掛載到vm上。
可能有人會好奇,在形參部分,Vue: Class<Component>
是什麼意思,因為JavaScript是一個動態類型語言,也就是說,聲明變數的時候不會指派他是任何一種類型的語言,像java就是典型的靜態類型語言。例如:boolean result = true
就是聲明result是一個布爾類型,而相對的,JavaScript中可以聲明var result =true
。這樣雖然方便很多,但是因為靜態類型在編譯過程中就會查出錯誤並提示開發者改正錯誤,但是像Js這樣的動態語言在編譯的時候既是存在錯誤也不會提出,只有在真正運行時才會出錯。所以就會有不必要的麻煩,那麼如何對Js進行靜態類型檢查呢?就是插件唄。vue用了flow的插件,讓js有了靜態類型檢查,:
後面代表了限定vue這個形參的屬性。具體就不展開了,可以去看flow的文檔。
Flow:https://flow.org/
接下來接著說正文,const vm: Component = this
可以看到把當前的執行前後文給了vm。然後之後就是一些陸陸續續的掛載,值得註意的就是vm.$options
就是填寫在vue實例里的參數,例如el
,mounted
,data
都被保存在$options
里。
但是平常使用的時候我們沒有用到this.$options.data1
里,反而是直接用this.data1
來調用,這其實vue也在其中進行了操作。
我們會發現在上面的代碼段里有一行initState(vm)
,我們找到initState的定義。
export function initState (vm: Component) {
// ..
const opts = vm.$options
if (opts.data) {
initData(vm)
}
// ..
}
然後我們可以接著轉到initData這個方法的定義
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
把上面的代碼拆分來看
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
上面代碼先通過$options獲取到data,然後判斷data是不是通過返回對象的方式建立的,如果是,那麼則執行getData方法。getData的方法主要操作就是 data.call(vm, vm)
這步通過給data調用了vm這個上下文環境,然後直接返回這個包括data的vm對象。
那麼現在vm上已經有data了是嗎?確實,但是這個data是vm._data
也就是說如果你想訪問message
這個屬性你現在只能通過vue._data.message
這樣來訪問。所以我們接著往下看。
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
這一大段上面聚焦的是prop data methods 們如果相同之後就會提出相應的警示。為什麼要他們不一樣呢,因為他們都是通過this.XX來調用的,如果重名,vue分不清他們是誰。如果都沒問題了,我們就把_datas上的值直接賦給vm,然後轉到最後一步proxy(vm, _data, key)
,然後我們轉移到proxy這個方法中:
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
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)
}
就是通過sharedPropertyDefinition.get
和sharedPropertyDefinition.set
的設置的get和set方法,然後在通過Object.defineProperty
來定義訪問target.key
的時候調用sharedPropertyDefinition
的set和get。
也就是相當於,我要求vm.message
,就會觸發sharedPropertyDefinition
的get,然後返回vm._data.message
至此數據就可以通過vm.message
的方式訪問了。