Vue源碼

參考文章:http://hcysun.me/2017/03/03/Vue%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/?utm_source=qq&utm_medium=social&utm_member=ZTZmNWZkYzIzMmQ2MjA0ZDNjNTA3ZjdhNDA2MjAzNDQ%3D%0A#%E4%B8%80%E3%80%81%E4%BB%8E%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%AA%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E5%85%A5%E6%89%8Bhtml

vue3.0 嚐鮮 -- 摒棄 Object.defineProperty,基於 Proxy 的觀察者機制探索

Vue.js 源碼:https://github.com/vuejs/vuevue

Vue技術內幕:http://hcysun.me/vue-design/node

源碼分析文檔:https://ustbhuangyi.github.io/vue-analysis/react

一、知識點git

 二、https://ustbhuangyi.github.io/vue-analysis/prepare/flow.htmlgithub

Flow 是facebook出品的JavaScript靜態類型檢查工具。Vue.js的源碼就利用Fow作了靜態類型檢測正則表達式

爲何用Flow?segmentfault

由於JavaScript是動態類型語言(弱類型語言),雖然靈活,可是也容易出現隱患代碼。數組

類型檢查是當前動態類型語言的發展趨勢,所謂類型檢查,就是在編譯期儘早發現(由類型錯誤引發的)bug,又不影響代碼運行(不須要運行時動態檢查類型),使編寫 JavaScript 具備和編寫 Java 等強類型語言相近的體驗。瀏覽器

Flow的工做方式

  • 類型推斷:經過變量的使用上下文來推斷出變量類型,而後根據這些推斷來檢查類型。

1 /*@flow*/``
2 
3 function split(str) {
4   return str.split(' ')
5 }
6 
7 split(11)

Flow 檢查上述代碼後會報錯,由於函數 split 期待的參數是字符串,而咱們輸入了數字。

類型註釋

  • 類型註釋:事先註釋好咱們期待的類型,Flow 會基於這些註釋來判斷

1 /*@flow*/
2 
3 function add(x: number, y: number): number {
4   return x + y
5 }
6 
7 add('Hello', 11)

如今 Flow 就能檢查出錯誤,由於函數參數的期待類型爲數字,而咱們提供了字符串。

 Flow還支持一些常見的類型註釋:數組、類和對象、Null

三、Vue.js源碼目錄設計    https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html

Vue.js 的源碼都在 src 目錄下,其目錄結構以下:

1 src
2 ├── compiler        # 編譯相關 
3 ├── core            # 核心代碼 
4 ├── platforms       # 不一樣平臺的支持
5 ├── server          # 服務端渲染
6 ├── sfc             # .vue 文件解析
7 ├── shared          # 共享代碼

從 Vue.js 的目錄設計能夠看到,做者把功能模塊拆分的很是清楚,相關的邏輯放在一個獨立的目錄下維護,而且把複用的代碼也抽成一個獨立目錄。

這樣的目錄設計讓代碼的閱讀性和可維護性都變強,是很是值得學習和推敲的。

 四、源碼構建  https://ustbhuangyi.github.io/vue-analysis/prepare/build.html#構建腳本

主要包括構建腳本和構建過程

Runtime Only VS Runtime + Compiler的區別

五、new Vue的時候,會把傳入的option都綁定到$options上,因此咱們能夠經過vm.$options或者this.$options獲取傳入的參數(如:el,data)

六、new Vue()的時候發生了什麼?

地址:https://ustbhuangyi.github.io/vue-analysis/data-driven/new-vue.html

new 關鍵字在 Javascript 語言中表明實例化是一個對象,而 Vue 其實是一個類,類在 Javascript 中是用 Function 來實現的,來看一下源碼,在src/core/instance/index.js 中。

會調用_init函數進行初始化。會初始化生命週期、事件、props、data、methods、computed與watch等。

最主要的是經過Object.defineProperty設置setter與getter函數,用來實現【響應式】與【依賴收集】

七、Vue 實例掛載的實現

Vue 中咱們是經過 $mount 實例方法去掛載 vm 的

八、Vue 的 _render 方法是實例的一個私有方法,它用來把實例渲染成一個虛擬 Node。它的定義在 src/core/instance/render.js 文件中:

九、其實 VNode 是對真實 DOM 的一種抽象描述,它的核心定義無非就幾個關鍵屬性,標籤名、數據、子節點、鍵值等,其它屬性都是都是用來擴展 VNode 的靈活性以及實現一些特殊 feature 的。因爲 VNode 只是用來映射到真實 DOM 的渲染,不須要包含操做 DOM 的方法,所以它是很是輕量和簡單的。

Virtual DOM 除了它的數據結構的定義,映射到真實的 DOM 實際上要經歷 VNode 的 create、diff、patch 等過程。那麼在 Vue.js 中,VNode 的 create 是經過以前提到的 createElement 方法建立的

十、Vue.js 利用 createElement 方法建立 VNode,它定義在 src/core/vdom/create-elemenet.js 中:

分爲倆部分:children 的規範化和VNode 的建立

那麼至此,咱們大體瞭解了 createElement 建立 VNode 的過程,每一個 VNode 有 childrenchildren 每一個元素也是一個 VNode,這樣就造成了一個 VNode Tree,它很好的描述了咱們的 DOM Tree。

十一、Vue 的 _update 是實例的一個私有方法,它被調用的時機有 2 個,一個是首次渲染,一個是數據更新的時候;因爲咱們這一章節只分析首次渲染部分,數據更新部分會在以後分析響應式原理的時候涉及。_update 方法的做用是把 VNode 渲染成真實的 DOM,它的定義在 src/core/instance/lifecycle.js 中:

十二、初始化 Vue 到最終渲染的整個過程。

從new Vue開始,先執行init,進行一堆初始化操做;而後進行掛載,執行$mount

$mount的執行過程:若是是帶編譯版本的(就是template這樣的,咱們通常寫的那種),就執行compile,生成render function;若是直接就是render function

獲得render function後,調用render方法,生成vnode,以後經過patch過程,生成真實的DOM

備註:在vue中,會把以前寫的真實的dom刪掉,而後經過Virtual DOM從新生成真實的dom

1三、

 1四、

知道了 3 種異步組件的實現方式,而且看到高級異步組件的實現是很是巧妙的,它實現了 loading、resolve、reject、timeout 4 種狀態。異步組件實現的本質是 2 次渲染,除了 0 delay 的高級異步組件第一次直接渲染成 loading 組件外,其它都是第一次渲染生成一個註釋節點,當異步獲取組件成功後,再經過 forceRender 強制從新渲染,這樣就能正確渲染出咱們異步加載的組件了。

1五、

defineReactive 函數最開始初始化 Dep 對象的實例,接着拿到 obj 的屬性描述符,而後對子對象遞歸調用 observe 方法,這樣就保證了不管 obj 的結構多複雜,它的全部子屬性也能變成響應式的對象,這樣咱們訪問或修改 obj 中一個嵌套較深的屬性,也能觸發 getter 和 setter。

1六、 Vue 會把普通對象變成響應式對象,響應式對象 getter 相關的邏輯就是作依賴收集

Vue爲數據中的每個key維護一個訂閱者列表。對於生成的數據,經過Object.defineProperty對其中的每個key進行處理,主要是爲每個key設置getset方法,以此來爲對應的key收集訂閱者,並在值改變時通知對應的訂閱者。

最開始new Vue(),初始化代碼的時候,就會進行依賴收集,以後改變值之後,會再次進行依賴收集

參考網站:https://www.jianshu.com/p/e6e1fa824849

 

 1七、計算屬性 computed的優化有倆處:  參考:https://ustbhuangyi.github.io/vue-analysis/reactive/computed-watcher.html#computed

(1)、當初始化computed的時候,會先進行收集依賴,但不會觸發watch,只有當渲染到computed計算的屬性時,就觸發了計算屬性的 getter,它會拿到計算屬性對應的 watcher,而後執行 watcher.depend()

(2)、一旦咱們對計算屬性依賴的數據作修改,則會觸發 setter 過程,通知全部訂閱它變化的 watcher 更新,執行 watcher.update() 方法

 

記住這裏:getAndInvoke 函數會從新計算,而後對比新舊值,若是變化了(就算依賴一直變化,只要最終值沒有,那就不會觸發回調函數)則執行回調函數,那麼這裏這個回調函數是 this.dep.notify(),在咱們這個場景下就是觸發了渲染 watcher 從新渲染。

 

 

1八、

 響應式原理圖

參考:https://segmentfault.com/q/1010000010977528

對上面的理解:頁面中所用到的數據data,渲染的時候就會觸發Object.defineReactive的getter方法

getter方法會將window.target(watch,每一個用到這個數據的地方,都會生成一個watch)保存到subs數組中

subs數組抽取出來,封裝到Dep類中,getter中要new Dep(),調用Dep類中的addSub,將watch保存到subs數組中 

數據改變的時候,就會觸發setter方法==》觸發Dep類中的notify方法==》遍歷subs數組==》觸發watch中的update方法

綜合:每個使用的數據都有一個單獨的Dep類,每一個Dep類中有多個watch(根據使用此數據來生成,一個地方使用就生成一個watch)

 1九、編譯入口邏輯之因此這麼繞,是由於 Vue.js 在不一樣的平臺下都會有編譯的過程,所以編譯過程當中的依賴的配置 baseOptions 會有所不一樣。而編譯過程會屢次執行,但這同一個平臺下每一次的編譯過程配置又是相同的,爲了避免讓這些配置在每次編譯過程都經過參數傳入,Vue.js 利用了函數柯里化的技巧很好的實現了 baseOptions 的參數保留。一樣,Vue.js 也是利用函數柯里化技巧把基礎的編譯過程函數抽出來,經過 createCompilerCreator(baseCompile) 的方式把真正編譯的過程和其它邏輯如對編譯配置處理、緩存處理等剝離開,這樣的設計仍是很是巧妙的。

 

parse 的目標是把 template 模板字符串轉換成 AST 樹,它是一種用 JavaScript 對象的形式來描述整個模板。那麼整個 parse 的過程是利用正則表達式順序解析模板,當解析到開始標籤、閉合標籤、文本的時候都會分別執行對應的回調函數,來達到構造 AST 樹的目的。

AST 元素節點總共有 3 種類型,type 爲 1 表示是普通元素,爲 2 表示是表達式,爲 3 表示是純文本。

 

optimize 的過程,就是深度遍歷這個 AST 樹,去檢測它的每一顆子樹是否是靜態節點,若是是靜態節點則它們生成 DOM 永遠不須要改變,這對運行時對模板的更新起到極大的優化做用。

咱們經過 optimize 咱們把整個 AST 樹中的每個 AST 元素節點標記了 static 和 staticRoot,它會影響咱們接下來執行代碼生成的過程。

20、event事件

注意:vm.$emit 是給當前的 vm 上派發的實例,之因此咱們經常使用它作父子組件通信,是由於它的回調函數的定義是在父組件中,對於咱們這個例子而言,當子組件的 button 被點擊了,它經過 this.$emit('select') 派發事件,那麼子組件的實例就監聽到了這個 select 事件,並執行它的回調函數——定義在父組件中的 selectHandler 方法,這樣就至關於完成了一次父子組件的通信。

 2一、v-model實際是一個語法糖

<input v-model="message">
<input :value="message" @input="message=$event.target.value">

這是一回事

 

v-model總結:

v-model是一個語法糖

在input中  這倆個是相等的

<input v-model="searchText">

等價於

<input :value="searchText" @input="searchText = $event.target.value">

固然,Vue還作了一些優化

在組件中

https://cn.vuejs.org/v2/guide/components.html#在組件上使用-v-model

<test v-model="searchText"></test>

等價於

<test :value="searchText" @input="searchText = $event"></test>

在test組件裏,要這樣寫

<template>
  <div class="test">
    <input type="checkbox"  :checked="value" @change="$emit('input', $event.target.checked)">  // 使用$emit向父組件傳回input事件
  </div>
</template>

<script>
  export default {
    name: 'test',
    props: ['value'] // 這個必須有,並且默認傳過來的就是value
  }
</script>

在實際中,咱們傳遞value和input有可能會照成歧義,這是咱們能夠自定義父組件傳過來的值和向父組件傳遞的事件

<template>
  <div class="test">
    <input type="checkbox"  :checked="checked" @change="$emit('changeChecked', $event.target.checked)">
  </div>
</template>

<script>
  export default {
    name: 'test',
    model: {
      prop: 'checked',
      event: 'changeChecked'
    },
    props: ['checked'] // 這個必須有,值就是咱們上面定義的prop
  }
</script>

 2二、slot

 普通插槽是在父組件編譯和渲染階段生成 vnodes,做爲當前組件渲染vnode的children,因此數據的做用域是父組件實例,子組件渲染的時候直接拿到這些渲染好的 vnodes

做用域插槽,父組件在編譯和渲染階段並不會直接生成 vnodes,而是在父節點 vnode 的 data 中保留一個 scopedSlots 對象,存儲着不一樣名稱的插槽以及它們對應的渲染函數,只有在編譯和渲染子組件階段纔會執行這個渲染函數生成 vnodes,因爲是在子組件環境執行的,因此對應的數據做用域是子組件實例。

 

兩種插槽的目的都是讓子組件 slot 佔位符生成的內容由父組件來決定,但數據的做用域會根據它們 vnodes 渲染時機不一樣而不一樣。

2三、keep-alive

<keep-alive> 組件是一個抽象組件,它的實現經過自定義 render 函數而且利用了插槽,而且知道了 <keep-alive> 緩存 vnode,瞭解組件包裹的子元素——也就是插槽是如何作更新的。且在 patch 過程當中對於已緩存的組件不會執行 mounted,因此不會有通常的組件的生命週期函數可是又提供了 activated 和 deactivated 鉤子函數。另外咱們還知道了 <keep-alive> 的 props 除了 include 和 exclude 還有文檔中沒有提到的 max,它能控制咱們緩存的個數。

2四、

 

 2五、vue-touter

        參考網站:https://github.com/DDFE/DDFE-blog/issues/9

 

 導航守衛的參考:http://www.javashuo.com/article/p-ahbhkegh-v.html

 

完整的導航解析流程

  1. 導航被觸發。
  2. 在失活的組件裏調用離開守衛。
  3. 調用全局的 beforeEach 守衛。
  4. 在重用的組件裏調用 beforeRouteUpdate 守衛 (2.2+)。
  5. 在路由配置裏調用 beforeEnter
  6. 解析異步路由組件。
  7. 在被激活的組件裏調用 beforeRouteEnter
  8. 調用全局的 beforeResolve 守衛 (2.5+)。
  9. 導航被確認。
  10. 調用全局的 afterEach 鉤子。
  11. 觸發 DOM 更新。
  12. 用建立好的實例調用 beforeRouteEnter 守衛中傳給 next 的回調函數。

 

Vue-Router 內置了一個組件 <router-view>

Vue-Router 還內置了另外一個組件 <router-link>

<router-link> 比起<a href="..."> 會好一些,理由以下:

不管是 HTML5 history 模式仍是 hash 模式,它的表現行爲一致,因此,當你要切換路由模式,或者在 IE9 降級使用 hash 模式,無須做任何變更。

在 HTML5 history 模式下,router-link 會守衛點擊事件,讓瀏覽器再也不從新加載頁面。

當你在 HTML5 history 模式下使用 base 選項以後,全部的 to 屬性都不須要寫(基路徑)了。

 

路徑變化是路由中最重要的功能,咱們要記住如下內容:路由始終會維護當前的線路,路由切換的時候會把當前線路切換到目標線路,切換過程當中會執行一系列的導航守衛鉤子函數,會更改 url,一樣也會渲染對應的組件,切換完畢後會把目標線路更新替換當前線路,這樣就會做爲下一次的路徑切換的依據。

 

 2六、Vuex

Vuex 的初始化過程就分析完畢了,除了安裝部分,咱們重點分析了 Store 的實例化過程。咱們要把 store 想象成一個數據倉庫,初始化Vuex,就是在實例化Vuex,爲了更方便的管理倉庫,咱們把一個大的 store 拆成一些 modules,整個 modules 是一個樹型結構。每一個 module 又分別定義了 stategettersmutationsactions,咱們也經過遞歸遍歷模塊的方式都完成了它們的初始化。爲了 module 具備更高的封裝度和複用性,還定義了 namespace 的概念。最後咱們還定義了一個內部的 Vue 實例,用來創建 state 到 getters 的聯繫,而且能夠在嚴格模式下監測 state 的變化是否是來自外部,確保改變 state 的惟一途徑就是顯式地提交 mutation

 

Vuex 提供的一些經常使用 API 咱們就分析完了,包括數據的存取、語法糖、模塊的動態更新等。要理解 Vuex 提供這些 API 都是方便咱們在對 store 作各類操做來完成各類能力,尤爲是 mapXXX 的設計,讓咱們在使用 API 的時候更加方便,這也是咱們從此在設計一些 JavaScript 庫的時候,從 API 設計角度中應該學習的方向。

相關文章
相關標籤/搜索