Vue源碼筆記本(一)
(該文章對src/core/instance下的文件的代碼功能做了注解,便於大家在看源碼過程中快速理解)
入口文件 src/core/instance/index.js 中可以看到
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
Vue的構造函數在此, 然後通過mixin的方式將
- 數據方法等特性處理(state)
- 事件處理(events)
- 生命周期(lifecycle)
- 渲染函數(render)
的相關的方法掛到Vue的原型上去。
1.init
掛上了_init 方法,沒錯就是 構造函數當中調用的_init方法。
這裏做了一些參數處理,其中作者在參數對象賦值的時候,用一個個賦值代替列舉賦值來提升性能。 這樣注釋的: // doing this because it's faster than dynamic enumeration.
然後就是依次執行以下方法,包括各個init函數和觸發鉤子
/* 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')
initState(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
2.state
initState一共做了如下操作
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) initWatch(vm, opts.watch)
initProps和initData的處理類似:
initProps做了類型檢驗,做了defineReactive就是響應式處理;
initData做了函數處理(data是對象或者函數),然後檢查key是否與props重複,還檢查了key是否$ 或者 _ 開頭,這是vue內部使用, 如果你的key以此開頭,你就會拿不到你的key了(這個好像沒看到說明,感覺可以提示下)
然後做了個proxy,把用戶參數props和data分別代理到 _props 和 _data 這2個內部屬性,用戶訪問其實訪問到了_props和_data下。
initMethods 這個就是把methods內部的函數bind到實例然後掛到實例
initWatch 通過遍曆然後執行$watch方法來處理
initComputed 遍曆為創建一個 Watcher 實例,然後放到_computedWatchers
(響應式相關後續再展開)
stateMixin 混入方法掛了$data,$props,$set,$delete等讓數據操作指向_data 和 _props,然後掛了$watch 方法
3.events
掛載$on,$once,$off,$emit 四個方法
事件句柄都保存在 _events 這個屬性裏,在$on方法裏做了個處理,判斷該實例是否有使用生命周期的hook,如果沒有_hasHookEvent這個值是false,然後在lifecycle的callHook方法裏將不會$emit生命周期hook,是一個性能優化。
4.lifecycle
在原型上掛上 _mount 方法,這個比較重要, $mount 用的就是方法, ($mount在上麵的init函數被調用)這個方法運行的時候需要用到render函數裏了,沒有會報錯(關於這點,vue會把template或者el裏的html模版最終轉成render函數,一般如果我們用vue-loader開發,已經把template裏的內容轉成了render函數,所以最後打包出來的不會含有轉化template的功能代碼),render函數返回的是一個vnode,下麵貼個_mount部分代碼:
callHook(vm, 'beforeMount')
vm._watcher = new Watcher(vm, function updateComponent () {
vm._update(vm._render(), hydrating)
}, noop)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
這裏可以看到2個生命周期了
_update 方法開始去做dom更新了,看_update方法
一開始用_isMounted判斷是否觸發beforeUpdate鉤子,因為第一次插入dom也是調用這個方法,但是不應該觸發beforeUpdate
然後就去調用 patch 方法去做diff 然後更新dom了。(vnode以後再看)
另外,還掛載了$forceUpdate和$destroy 這2個方法
5.render
先掛載了 $nextTick 方法.
然後在renderMixin函數內往原型掛載了_render 方法(在instancei 下有個render-helpers文件夾裏麵有處理render相關的工具函數供調用)這個方法做了slot相關的操作,然後調用render函數拿到vnode ( render 相關的需要具體展開)
這裏有個_renderProxy ,在非生產環境下做了proxy,看 proxy.js ,就是在訪問實例屬性的時候,訪問不存在的屬性的時候給予提示.
以上。
最後更新:2017-04-07 21:05:52