【vue真的香】開始讀原始碼

lionel愛學習發表於2019-04-20

起源

這周經歷的事情有點多, 從週一的一面,到週二的二面到週三的口頭offer,週四的體檢, 最後再到沒收到正式offer,主要原因是在背調的時候發現虛報了原始薪資(10k報12k)和學歷問題(我是延畢的)。認清了自己的品質並不好,還是要誠信誒。

在小公司工作了兩年,迫切希望能在中大型公司中工作。對於這次的失利,還是挺難受的。恰逢週末,通讀了《深入淺出vue.js》,寫點零零散散的記錄吧。因為只是想寫點東西,研究的不透徹不到位不一定正確,下文質量奇差,閱讀過程請注意,請見諒。

在開始讀vue原始碼之前,學習資料繞不開Vue.js技術揭祕Vue技術內幕這兩個文件, 巧的是這兩篇原始碼解讀都是從 new Vue() 開始引導我們逐步進入Vue原始碼的大門, 但從《深入淺出vue.js》中展現了另外一種思路。

先看一下vue的定義

Vue (讀音 /vjuː/,類似於 view) 是一套用於構建使用者介面的漸進式框架。

加上我們平時使用的經驗, 這裡我思考下,vue的原始碼,是要實現什麼功能。就像在做一個專案的時候,我們需要進行需求定義一樣。

總結了一下,有些跳躍性思維:

  1. 釋出-訂閱者模式
  2. 虛擬DOM
  3. 模板編譯
  4. 擴充套件方法

在我的理解中, vue原始碼核心就是這四件事,其他的有跨平臺的包裝、提供vue的建構函式和程式碼結構等等。

一、釋出-訂閱者模式

這裡就不解釋這個模式的定義了。我們從程式碼方面考慮一下實現這個模式需要哪些類和方法。

首先我們都知道,實現這個模式的原理是通過Object.defineProperty(vue3.0要改成proxy)為data封裝了get和set方法。在getter中收集依賴, 在setter中觸發依賴。所以我們需要封裝一個函式,或者說是封裝一個類,可以拿到一個data,就把data中所有的屬性都用Object.defineProperty處理一下。這個類在vue原始碼中就是Observer,它的作用就是將一個資料內的所有屬性(包括紫屬性)都轉換成getter、setter的形式,然後追蹤它們的變化。

然後我們考慮一下getter中收集依賴的功能要怎麼實現,在js中用來存放資料的,我們首先想到的是用陣列,再考慮到getter中收集了依賴,可能還會觸發其他邏輯,例如需要增刪判重呀,所以我們又要封裝一個類。 這個類在vue原始碼中就是Dep類,它專門用來幫助我們管理依賴。使用這個類,我們可以收集依賴、刪除依賴或者向依賴傳送通知等。

再然後我們再想一下,什麼是依賴呢,不可能我在記錄依賴中就記入用到data的檔名和程式碼行位置吧。所以我們又需要封裝一個類來代表依賴。這個類在xue原始碼中就是Watcher類。Watcher類本質是一箇中介,資料發生變化時通知他,然後它再通知其他地方。在我們自己的程式碼編譯階段,每當遇到一個使用data的地方, 就在那個地方new一個Watcher,至於記錄位置嘛,直接把this傳進去就好了呀。而且好玩的是,這new Watcher的時候,我們會觸發到data的getter方法(因為我們要把資料返回給那個位置嘛),這個getter就自動把Watcher加入到Dep中了,省下了我們還需要遍歷程式碼,再把Watcher加進Dep這個邏輯了。

這裡該有張圖,就隨便貼一張啦= =

alt

大概邏輯就完成了,剩下的就是打補丁,各種bug型別有:

  • object的資料可以用getter/setter,array咋辦,例如array可以直接push,才不會調你的setter呢。解決吧,找個攔截器,覆蓋Array原型上的push、pop、shift等方法,往裡一貼,乾脆就在攔截器中觸發依賴,bug解決,加薪。
  • 有一天我往object裡新增屬性、刪除屬性,用this.list[0] = 1的方式修改陣列時,發現並沒有觸發更新這個操作,使用者怎麼辦?提bug!程式設計師怎麼辦?打補丁!想一想defineProperty確實監聽不到,es6之前也不能模擬陣列的原生行為,那就讓使用者自己多注意解決吧,給你提供一個vm.$set,一個vm.$delete。好了,下班。程式碼沒bug,是你不會用。
  • 一上午只想到這兩,剩下還有的話大家自己發現吧。

二、虛擬DOM

首先老生常談一下,引入虛擬DOM後,是80%的場景下提高了渲染速度,而剩下20%反而變慢了。

作為一個框架,我們不僅要考慮減少程式設計師的工作量,還要考慮到使用者的體驗方面。為了從整體上提高渲染速度,聽說有個流行的概念叫virtual DOM,我們vue也要用。

虛擬DOM呢,用程式碼來實現,也就是一個類,在vue原始碼中叫做VNode類,我們用這個類來描述真實DOM元素,而且還可以擴充套件描述,比如說我們可以給vnode也增加分類,這個vnode叫做註釋節點,那個叫文字節點,還有個叫元素節點,還有其他的這裡不擴充套件。

有了虛擬DOM,最終還是要渲染到真實DOM上,這個過程就叫patch,這個在原始碼中也是大頭了,但我們不具體講了,我們先只要知道patch做的事,本質上是新增節點,刪除節點和更新節點。原始碼中大量的程式碼都是儘可能的優化這個過程。

三、模板編譯

vue這個邏輯也很清晰,齊步三步走:1.解析器,2.優化器,3.程式碼生成器。

平時我們在寫.vue檔案時,template中的程式碼看上去和html結構一樣,但它為啥能實現v-for, v-if, {{message}}呢,那就是因為我們看到的網頁,並不是我們的template,而是被vue編譯過的。

首先,逐行解析我們的程式碼,用的是HTML解析器和文字解析器,把程式碼轉換成AST(抽象語法樹)。

然後,對AST使用優化器優化,例如標記靜態節點啥的。

最後,通過程式碼生成器,把AST編譯成可呼叫的JS語句。由此就可以去生成virtual DOM了。

四、擴充套件方法 通過以上三個模組,這個框架就歪歪斜斜的搭建起來了。在我們不得不用jquery來寫程式碼的時候,也可以利用上面的思路。

為了使用者的體驗和邏輯的完整,vue的餘下程式碼中提供了很多方法。包括:vm.$onvm.$emitvm.$nextTickVue.extendVue.filter,生命週期鉤子函式等等。

總結

本文寫了點對vue原始碼的大綱整理筆記,可食用性不高。

相關文章