vue 版本爲 2.6.11 博客的篇幅有點大,若是以爲比較繁瑣的,能夠跳着看,裏面也沒有粘大量的源碼,我會吧git上源碼的連接貼出來,你們能夠對照着源碼連接或者把源碼下載下來對照着看
看了好久的vue 的源碼,也看了好多關於源碼的貼子,本身也嘗試了寫了好幾回vue源碼的帖子,一是以爲寫的沒有章法思路不夠清晰,二是以爲vue3都出了我如今寫vue2的源碼學習有點晚了因此沒有發表,後來想一想本身寫出來能夠沉澱一些東西,而且也給你們提供一個閱讀源碼的思路, 但願能寫出一篇對我和對你們都有益處的帖子html
首先設定目標, 咱們不可能一行不差的把vue
的源碼都看一遍(若是每行都看很快就會失去方向和興趣),因此咱們要知道咱們看源碼的目標是什麼,以及看到什麼程度就認爲吧vue 的真正的核心代碼和思想都學會了。
我認爲把下面的這些點都瞭解的透徹了就算是真正的學到了vue 的精髓vue
上面的點就是咱們學習的目標,必定要先設定目標,要不就不知道咱們學習源碼的意義,學着學着就放棄了,咱們要學會以目標爲導向。node
首先咱們找到 vue
項目的入口而後咱們從我認爲vue中最重要的最核心的內容響應式原理
開始學起react
若是想要知道如何找到入口請看 找入口的思路,若是以爲不必看能夠跳過 git
入口文件爲platforms/web/entry-runtime.js
或者 platforms/web/entry-runtime-with-compiler.js
前者爲不帶 compiler
的後者爲帶有 compiler
的,由於咱們的目標設定中有 compiler
的內容,因此咱們就選後者爲咱們本次源碼學習的入口,對文件的依賴進行分析找到 VUe 的構造函數文件在 core/instance/index.js
這個文件中github
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) // 添加 _init 方法 stateMixin(Vue) // $set $delete $watch $data $props eventsMixin(Vue) //$on $once $off $emit lifecycleMixin(Vue) //_update $forceUpdate $destroy renderMixin(Vue) //$nextTick _render
這個文件的主要做用是定義了Vue 並豐富了他的原型上的方法(我在上面的註釋中標註了每一個方法分別對應了添加了那些原型方法),看一下vue的構函數,發現只調用了 _init
方法,整個Vue的入口就是 _init
這個方法了web
咱們對 _init
方法的內容進行分析面試
Vue.prototype._init = function (options?: Object) { ... if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm) } initLifecycle(vm) initEvents(vm) initRender(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) } ... }
我這裏省略了一部分源碼,你們能夠對照着本身下載下來的源碼看,這個 _init
方法作了作了好多的事情,咱們不可能在這裏一次說清楚,而且,咱們要知道咱們真正想要看的是什麼,找到這個文件的目的是爲了讓咱們更好的達成目標(固然不是說你們就不用看阿,只是不要太深究,由於每個方法都有特別深的調用鏈,若是看下去確定會失去方向跑偏的哦),經過語義上看,咱們要看的響應式原理應該從 initState
方法入手 往下深刻的學習(vue的命名規範很是的好,經過命名我就能輕鬆的看出來每一個方法實現的功能,也是咱們值得學習的地方)。express
如何找到入口呢,其實寫框架和咱們日常寫代碼同樣,想一想,咱們若是接觸到一個陌生的項目應該如何找到項目的入口呢,確定是先看 package.json
,而後分析其中的 script
,根據經驗和語義的判斷得出(你們也能夠看看vue 的開發者文檔之類的也能找到一些介紹,我以爲找一個入口不想太費時間全部就沒有那麼嚴謹的去看),vue 打包的命令應該爲 "build": "node scripts/build.js",
,咱們如今分析這個文件,這個文件不用太仔細的看,若是真的想要學習 rollup 打包的話能夠深刻的看一看,咱們的目的是找到項目真真的入口,在這裏深刻的去學習只會把咱們帶偏。json
let builds = require('./config').getAllBuilds()
const builds = { ... 'web-runtime-esm': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.esm.js'), ... }, 'web-full-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.js'), }, ... }
上面我粘出來的代碼就是咱們所尋找的入口文件,我是如何定位到這兩個的呢,經過看package.json
中的 main,module,unpkg
這三個配置分別爲咱們使用vue 庫時候的 cjs,esm,cdn
的引入文件,他們對應的entry
就是入口文件了。
首先咱們看vue
的官方文檔,對響應式原理的介紹很是的詳細(必定要看,必定要看,而且要記到內心,深入理解,面試的時候會問的哦 ),我這裏就不在多作贅述 官方文檔
首先用一句話來解釋響應式原理 當組件的數據發生變化的時候觸發組件的從新渲染
響應式原理核心的點有哪些,也就是咱們學習響應式原理最終要學會什麼,也就是咱們細分的 目標
我認爲吧上面的幾個點搞明白也就真正的搞明白了響應式原理
下面咱們從源碼入手開始分析響應式原理
原本準備從 _init
中調用的 _initState
中深刻分析的,可是其實你在看了 _initState
方法以後你會發現,這裏面大量的依賴了 watcher observer 和dep
這幾個類,因此咱們先把這幾個類徹底的搞清楚再看 _initState
方法這樣有助於咱們的閱讀
閱讀源碼不必定非要按照調用棧一層層的扒代碼學習,當你發現這部分代碼強烈的依賴另外一部分代碼的時候,能夠先搞懂他依賴的那一部分,這樣更方便咱們的理解。
咱們首先想一想 vue 的實現響應式原理的邏輯,再去看這三個類,若是咱們忘了核心的思想去看代碼會特別的迷茫,就像沒有需求寫代碼同樣,而且在咱們本身寫代碼的過程當中也是先想清楚了這個類要實現什麼功能,再動手去寫這個類,回顧一下思想, 大體流程是 爲data設置getter 和setter, getter 的時候收集依賴,setter 的時候調用依賴的回調。
源代碼通常篇幅比較大調用棧比較深,當遇到不理解的問題時回顧基本原理和目標,就不會丟了方向了。
若是不想看代碼細節的同窗,能夠跳過下面的代碼細節,這裏總結了一下 Observer watcher 和dep 各自的主要功能和他們之間的關係
observer
主要功能,將傳入的data 轉換爲Observer data ,就是設置新的get 和set 方法
dep
對象主要是數據和 watcher 之間的一個橋樑,存放的是數據更新時須要調用的 watcher,並在數據更新的時候觸發全部watcher 的update
watcher
對象是監控數據變化並調用回調,與dep的關係是,dep觸發watcher 的更新,一個watcher能夠有多個dep
咱們大體的瀏覽這個中的內容,導出了一個 Observer
類,defineReactive 和 observe
這個方法,其餘的方法先不看,等用到的時候再看。
observe方法
__ob__
則 ob = value.__ob__
shouldObserve==true
加上其餘的一些校驗經過的話 ob = new Observer(value)
asRootData && ob
則 vmCount++
若是是組件的跟data對象 記錄一下 vmCount總的來講這個方法就是 new Observer 接下來咱們看一下Observer 類
Observer類
構造函數 constructor(value: any)
this.dep = new Dep()
this.vmCount = 0
記錄依賴組件的數量__ob__
屬性上observeArray
不然調用 walk
walk(obj: Object) 方法
循環對象的keys,調用 defineReactive
從新設置屬性的 get和set
observeArray(items: Array<any>) 方法
循環數組對象,調用observe(item[i]),吧數組中的每個對象都設置爲響應式數據
defineReactive方法
主要參數: obj 對象, key修改的字段
主要功能: 1.獲取當前傳入key對應值的 Observer 實例 2. 爲 傳入對象的key屬性設置get和set
咱們知道vue 響應式是在 get 的時候收集依賴,在set 的時候調用回調的,咱們看看這裏是如何收集和調用回調的呢?
const dep = new Dep() ... let childOb = !shallow && observe(val) ... get: function reactiveGetter() { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() }
get 方法調用 dep 的depend 的方法收集依賴
set 方法調用 dep 的 notify 發通知更改
總結:Observer 的使命就是吧data 轉換爲一個響應式的數據,數據添加 __ob__
和 get set
方法收集依賴和通知更新
屬性介紹 1. 是id
從0 開始計數,每次new 一個新實例加一 2. subs:指訂閱了該dep的全部watcher 對象
方法介紹
addSub
添加訂閱者removeSub
刪除指定訂閱者depend
收集依賴notify
通知更新,遍歷全部的訂閱者,調用訂閱者(watcher)的update
方法
靜態變量target
當前的系統中的 watcher
對象 ,同一時刻只能有一個存在,經過調用 pushTarget 和 popTarget
這兩個方法來設置
構造函數邏輯
isRenderWatcher 狀況爲vm._watcher = this
賦值 (在render的時候會用到能夠先不關注)vm._watchers
對象中,vm._watchers
中包含當前vm的全部 watcher對象把options 的值賦值到 this 對象上,須要特別關注的幾個屬性
expOrFn
轉化而來的一個 function
,若是expOrFn
的值爲一個function
則getter 爲該方法,不然調用parsePath
方法將表達式轉換爲一個方法,此時getter方法的返回值爲監控的數據 (line 79~91)get方法的邏輯
dep 中的 pushTarget
方法把dep中的 Dep.target
設置爲當前的 watcher 對象,表示後面全部的依賴收集都收集到當前的 watcher
對象上this.getter
方法實現依賴的收集,this.getter 方法執行過程當中全部被observer
過的對象的依賴都會收集到當前的能夠 watcher
對象上,deep爲true
則調用traverse
方法對最終的value進行遞歸,實現對全部的子屬性的依賴收集,最後調用 dep中的popTarget
方法,關掉收集,並調用 cleanupDeps
方法addDep方法的邏輯
cleanupDeps方法的邏輯
get 方法調用的
update方法的邏輯
dep 的notify 方法中調用的
在監聽數據發生變化的時候更新,若是 lazy==true
則this.dirty = true
不更新,若是sync爲true 則同步更新不然調用 queueWatcher(this)
方法放入隊列執行
run方法
run方法主要是從新計算value, 並執行回調
evaluate 方法
這個方法只在lazy爲true的時候調用
計算value 的值, 並吧dirty 設置爲false
teardown方法
清空watcher 對象上的依賴,以及清空 dep 上的subs 的watcher 對象,而且吧 this.active 設置爲 false
入口方法_init
中調用的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) } }
initState 方法定義了 _watchers
變量, 並調用 initProps, initMethods, initData ,initComputed, initWatcher
分別對 props, methods, data,computed,watcher
進行初始化
initProps
vm._props
proxy(vm, '_props', key)
這個把propsData 中的數據經過代理的方式掛到 vm 上initData
vm._data
賦值,若是 options.data
是一個方法則調用 getData(data,vm)
不然直接返回 options.data
, getData
方法主要是執行了 options.data
方法proxy(vm, "_data", key)
方法,將_data上的屬性使用代理的方式掛到vm 上observe(data, true);
方法吧 data對象轉換爲 observe 以後的對象initComputed
vm._computedWatchers
options.computed
將 computed 中的每個方法 new 一個 Watcher 對象放到了 vm._computedWatchers
中,須要注意的是 這裏 watcher 對象沒有回調,而且設置了 options的{lazy: true}
這意味着,computed 的方法不是當即被調用的defineComputed
方法把 compute 上的每一個key 做爲一個變量掛到的 vm
上,變量的get方法是經過調用 createComputedGetter
這個方法返回的一個方法createComputedGetter
方法:根據傳入的key 調用_computedWatchers[key]的 get方法, 最後返回watcher 的 valueinitWatch
循環 options.watcher
而且調用 createWatcher
,createWatcher
又調用了vm.$watch
, 這個方法 new 了一個 Watcher對象
initMethods
內容比較簡單主要是將 options.methods
中的方法掛載到 vm
對象上並別將 this 指向了 vm
我尚未看vue3 的源碼等看完以後在補充上來