作為MVVM框架的一種,Vue最為人津津樂道的當是數據與視圖的綁定,將直接操作DOM節點變為修改 data 數據,利用 Virtual Dom 來 Diff 對比新舊視圖,從而實現更新。不僅如此,還可以通過 Vue.prototype.$watch 來監聽 data 的變化并執行回調函數,實現自定義的邏輯。雖然日常的編碼運用已經駕輕就熟,但未曾去深究技術背后的實現原理。作為一個好學的程序員,知其然更要知其所以然,本文將從源碼的角度來對Vue響應式數據中的觀察者模式進行簡析。
初始化 Vue 實例
在閱讀源碼時,因為文件繁多,引用復雜往往使我們不容易抓住重點,這里我們需要找到一個入口文件,從 Vue 構造函數開始,拋開其他無關因素,一步步理解響應式數據的實現原理。首先我們找到 Vue 構造函數:
// src/core/instance/index.jsfunction 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)}// src/core/instance/init.jsVue.prototype._init = function (options) { ... // a flag to avoid this being observed vm._isVue = true // merge options // 初始化vm實例的$options if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } ... initLifecycle(vm) // 梳理實例的parent、root、children和refs,并初始化一些與生命周期相關的實例屬性 initEvents(vm) // 初始化實例的listeners initRender(vm) // 初始化插槽,綁定createElement函數的vm實例 callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') if (vm.$options.el) { vm.$mount(vm.$options.el) // 掛載組件到節點 }}為了方便閱讀,我們去除了 flow 類型檢查和部分無關代碼。可以看到,在實例化Vue組件時,會調用 Vue.prototype._init ,而在方法內部,數據的初始化操作主要在 initState (這里的 initInjections 和 initProvide 與 initProps 類似,在理解了 initState 原理后自然明白),因此我們重點來關注 initState 。
// src/core/instance/state.jsexport function initState (vm) { 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) }}
新聞熱點
疑難解答
圖片精選