前端技術日新月異,過一段時間就會涌現一些新的技術框架或者概念。並且目前使用最廣泛的三大前端庫,也不斷地在更新版本增加新特性。對於前端開發人員來說,都有種學不動的無力感,還要面對來自“后浪”的挑戰。因此提升技術的深度和廣度,是塑造自我核心競爭力的關鍵一步。今天咋們就來閱讀理解Vue的源碼,知道我們平時在開發過程中經常使用的屬性、生命周期以及數據雙向綁定的實現機制。
一、項目結構
首先我們需要將Vue源碼從github上拉倒本地,現在最新版本的Vue加入了typescript類型檢測。我們都知道javascript是弱類型語言,書寫代碼的時候沒有對變量的類型進行定義,后期擴展、團隊開發中很容易犯錯。大家還不了解typescript可以先看官方文檔,不然源碼中的語法會存在一定的閱讀障礙。
項目中我們重點關注src下面的complier(編譯相關)、core(核心代碼邏輯)、platforms(平台相關)這三部分內容,基本涵蓋了平常工程化開發中所用到的知識的底層原理。
如果需要在本地對源碼進行斷點調試,可以按照下圖進行配置:
運行npm run dev命令后,會在dist目錄下生成未壓縮、混淆的vue.js文件。引用該文件就能在源碼上打斷點調試了。
二、定義Vue構造函數
core/instance/index.js中定義了Vue構造函數並暴露出去,屬於原生javascript面向對象編程思想,結合工廠模式和原型鏈模式實現繼承。
在代碼中我們會看到許多process變量,這個屬於node.js中的對象,根據打包命令能判斷環境變量區分開發環境、生產環境,繼而走不同的代碼邏輯。
Vue函數體中執行了_init方法,根據實例化vue傳入的options不同生成不同的對象,_init方法在initMixin后進行了混入。
下面的幾個mixin方法,就是在Vue對象的原型上混入公共方法,包括初始化、state數據、事件處理、生命周期、render。
三、_init方法
core/instance/init.js文件中我們看到_init方法定義在Vue.prototype原型上。函數體中排除process對於環境變量的判斷,其實代碼量不是很多。
這部分代碼判斷vue實例是否是組件,不是組件就將傳入的options在內部進行合並,保留vm對象的完整性。
這部分就會初始化生命周期、事件、渲染頁面、state數據。callHook方法用於調用組件內部相應的生命周期鈎子,看到這里我們就很好理解為什么組件中created鈎子是在頁面數據初始化之后進行調用,並且inject數據在data/props之前就能獲取到。
最后就是將vue實例$mount掛載到對應的dom節點上。
四、callHook函數
core/instance/lifecycle.js中看到callHook這個方法接受兩個參數vm、hook,會獲取options參數中傳入的對應鈎子的執行函數。然后通過invokeWithErrorHandling這個方法進行處理。
invokeWithErrorHandling方法定義在core/util/error.js中,核心邏輯就是紅框中標注的部分,在vm實例對象上通過apply、call進行調用執行。並且鈎子函數本身就是promise,也會返回promise對象,可以通過catch進行錯誤捕獲。
現在看來,生命周期中的鈎子函數,相當於在組件進行特定操作后,比如:數據初始化、dom重繪、組件銷毀等,Vue庫內部提供的可以在自定義組件中進行副作用操作的鈎子。至於數據更新如何改變虛擬dom,繼而觸發頁面重繪的邏輯,都是在框架內部進行處理的。
五、initState初始化組件數據
core/instance/state.js中initState函數會初始化props、methods、data、computed、watch這一系列屬性。我們重點看initData這個方法的邏輯、
先是獲取options上的data對象,判斷是否是函數然后走不同的邏輯,並且將數據賦值給vm._data私有變量。
接下來這段代碼就很重要,有幾個作用:
- hasOwn判斷methods、props中是否有同名屬性;
- props中如果沒有同名的屬性,isReserved方法判斷變量名是否以“$”、"_"兩個特俗字符開頭,這兩個屬於特定字符適用於在類中定義私有屬性;
- proxy方法通過Object.defineProperty(target, propKey, propDesc)方法將vm.optios.data上的數據做了一層代理。能直接在vm對象對象上進行set賦值、get取值操作;
最后observe是new Observer(value)的實例,是基於Vue實現了觀察者模式,監聽數據變化,更新vNode重繪UI頁面。有興趣的同學可以看看這篇文章Vue源碼閱讀之觀察者模式(三)
我目前是通過定義Vue構造函數->_init()->initState()->initData()這一條線來閱讀源碼。誠然里面的知識點、細節很多,需要花時間鑽研!
最后上一張流程圖:
系列相關文章: