起源
這周經歷的事情有點多, 從週一的一面,到週二的二面到週三的口頭offer,週四的體檢, 最後再到沒收到正式offer,主要原因是在背調的時候發現虛報了原始薪資(10k報12k)和學歷問題(我是延畢的)。認清了自己的品質並不好,還是要誠信誒。
在小公司工作了兩年,迫切希望能在中大型公司中工作。對於這次的失利,還是挺難受的。恰逢週末,通讀了《深入淺出vue.js》,寫點零零散散的記錄吧。因為只是想寫點東西,研究的不透徹不到位不一定正確,下文質量奇差,閱讀過程請注意,請見諒。
在開始讀vue原始碼之前,學習資料繞不開Vue.js技術揭祕和Vue技術內幕這兩個文件, 巧的是這兩篇原始碼解讀都是從 new Vue()
開始引導我們逐步進入Vue原始碼的大門, 但從《深入淺出vue.js》中展現了另外一種思路。
先看一下vue的定義
Vue (讀音 /vjuː/,類似於 view) 是一套用於構建使用者介面的漸進式框架。
加上我們平時使用的經驗, 這裡我思考下,vue的原始碼,是要實現什麼功能。就像在做一個專案的時候,我們需要進行需求定義一樣。
總結了一下,有些跳躍性思維:
- 釋出-訂閱者模式
- 虛擬DOM
- 模板編譯
- 擴充套件方法
在我的理解中, 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這個邏輯了。
這裡該有張圖,就隨便貼一張啦= =
大概邏輯就完成了,剩下的就是打補丁,各種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.$on
,vm.$emit
,vm.$nextTick
,Vue.extend
,Vue.filter
,生命週期鉤子函式等等。
總結
本文寫了點對vue原始碼的大綱整理筆記,可食用性不高。