1. 程式人生 > >Vue原始碼閱讀

Vue原始碼閱讀

vue已是目前國內前端web端三分天下之一,同時也作為本人主要技術棧之一,在日常使用中知其然也好奇著所以然,另外最近的社群湧現了一大票vue原始碼閱讀類的文章,在下借這個機會從大家的文章和討論中汲取了一些營養,同時對一些閱讀原始碼時的想法進行總結,出產一些文章,作為自己思考的總結,本人水平有限,歡迎留言討論~

目標Vue版本:2.5.17-beta.0

宣告:文章中原始碼的語法都使用 Flow,並且原始碼根據需要都有刪節(為了不被迷糊 @[email protected]),如果要看完整版的請進入上面的github地址,本文是系列文章,文章地址見底部~

1. 響應式系統

通過官網的介紹我們知道 Vue.js 是一個MVVM框架,它並不關心檢視變化,而通過資料驅動檢視更新,這讓我們的狀態管理非常簡單,而這是怎麼實現的呢。盜用官網一張圖


每個元件例項都有相應的 Watcher 例項物件,它會在元件渲染的過程中把屬性記錄為依賴,之後當依賴項的 setter 被呼叫時,會通知 watcher 重新計算,從而致使它關聯的元件得以更新。

這裡有三個重要的概念 ObserveDepWatcher,分別位於src/core/observer/index.jssrc/core/observer/dep.jssrc/core/observer/watcher.js

  • Observe 類主要給響應式物件的屬性新增 getter/setter 用於依賴收集與派發更新
  • Dep 類用於收集當前響應式物件的依賴關係
  • Watcher 類是觀察者,例項分為渲染 watcher、計算屬性 watcher、偵聽器 watcher三種

2. 程式碼實現

2.1 initState

響應式化的入口位於 src/core/instance/init.js 的 initState 中:

123456789101112 // src/core/instance/state.jsexport functioninitState(vm:Component){constopts=vm.$optionsif(opts.props)initProps(vm,opts.props)// 初始化propsif(opts.methods)initMethods(vm,opts.methods)// 初始化methodsif(opts.data)initData(vm)// 初始化dataif(opts.computed)initComputed(vm,opts.computed)// 初始化computedif(opts.watch)initWatch(vm,opts.watch)// 初始化watch}}

它非常規律的定義了幾個方法來初始化 propsmethodsdatacomputedwathcer,這裡看一下 initData 方法,來窺一豹

12345678910 // src/core/instance/state.jsfunctioninitData(vm:Component){let data=vm.$options.datadata=vm._data=typeof data==='function'?getData(data,vm):data||{}observe(data,true/* asRootData */)// 給data做響應式處理}複製程式碼

首先判斷了下 data 是不是函式,是則取返回值不是則取自身,之後有一個 observe 方法對 data 進行處理,這個方法嘗試給建立一個Observer例項 __ob__,如果成功建立則返回新的Observer例項,如果已有Observer例項則返回現有的Observer例項

2.2 Observer/defineReactive

12345678 // src/core/observer/index.jsexport functionobserve(value:any,asRootData:?boolean):Observer|void{let ob:Observer|voidob=newObserver(value)returnob}

這個方法主要用 data 作為引數去例項化一個 Observer 物件例項,Observer 是一個 Class,用於依賴收集和 notify 更新,Observer 的建構函式使用 defineReactive 方法給物件的鍵響應式化,給物件的屬性遞迴新增 getter/setter ,當data被取值的時候觸發 getter 並蒐集依賴,當被修改值的時候先觸發 getter 再觸發 setter 並派發更新

JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051 // src/core/observer/index.jsexportclassObserver{value:any;dep:Dep;constructor(value:any){value:any;this.dep=newDep()def(value,'__ob__',this)// def方法保證不可列舉this.walk(value)}// 遍歷物件的每一個屬性並將它們轉換為getter/setterwalk(obj:Object){constkeys=Object.keys(obj)for(leti=0;i<keys.length;i++){// 把所有可遍歷的物件響應式化defineReactive(obj,keys[i])}}}export functiondefineReactive(obj:Object,key:string,val:any,customSetter?:?Function,shallow?:boolean){constdep=newDep()// 在每個響應式鍵值的閉包中定義一個dep物件// 如果之前該物件已經預設了getter/setter則將其快取,新定義的getter/setter中會將其執行constgetter=property&&property.getconstsetter=property&&property.setlet childOb=!shallow&&observe(val)Object.defineProperty(obj,key,{enumerable:true,configurable:true,get:functionreactiveGetter(){constvalue=getter?getter.call(obj):val// 如果原本物件擁有getter方法則執行if(Dep.target){// 如果當前有watcher在讀取當前值dep.depend()// 那麼進行依賴收集,dep.addSub}returnvalue},set:functionreactiveSetter(newVal){constvalue=getter?getter.call(obj):val// 先getterif(newVal===value||(newVal!==newVal&&value!==value)){// 如果跟原來值一樣則不管return}if(setter){setter.call(obj,newVal)}// 如果原本物件擁有setter方法則執行else{val=newVal}dep.notify()// 如果發生變更,則通知更新,呼叫watcher.update()}})}

getter 的時候進行依賴的收集,注意這裡,只有在 Dep.target 中有值的時候才會進行依賴收集,這個 Dep.target 是在Watcher例項的 get 方法呼叫的時候 pushTarget 會把當前取值的watcher推入 Dep.target,原先的watcher壓棧到 targetStack 棧中,當前取值的watcher取值結束後出棧並把原先的watcher值賦給 Dep.targetcleanupDeps 最後把新的 newDeps 裡已經沒有的watcher清空,以防止檢視上已經不需要的無用watcher觸發

setter 的時候首先 getter,並且比對舊值沒有變化則return,如果發生變更,則dep通知所有subs中存放的依賴本資料的Watcher例項 update 進行更新,這裡 update 中會 queueWatcher( ) 非同步推送到排程者觀察者佇列 queue 中,在nextTick時 flushSchedulerQueue( ) 把佇列中的watcher取出來執行 watcher.run 且執行相關鉤子函式

2.3 Dep

上面多次提到了一個關鍵詞 Dep,他是依賴收集的容器,或者稱為依賴蒐集器,他記錄了哪些Watcher依賴自己的變化,或者說,哪些Watcher訂閱了自己的變化;這裡引用一個網友的發言:

@liuhongyi0101 :簡單點說就是引用計數 ,誰借了我的錢,我就把那個人記下來,以後我的錢少了 我就通知他們說我沒錢了

而把借錢的人記下來的小本本就是這裡 Dep 例項裡的subs

JavaScript
123456789101112131415161718192021222324 // src/core/observer/dep.jslet uid=0// Dep例項的id,為了方便去重exportdefaultclassDep{static target:?Watcher// 當前是誰在進行依賴的收集id:numbersubs:Array<Watcher>// 觀察者集合constructor(){this.id=uid++// Dep例項的id,為了方便去重this.subs=[]// 儲存收集器中需要通知的Watcher}addSub(sub:Watcher){...}/* 新增一個觀察者物件 */removeSub(sub:Watcher){...}/* 移除一個觀察者物件 */depend(){...}/* 依賴收集,當存在Dep.target的時候把自己新增觀察者的依賴中 */notify(){...}/* 通知所有訂閱者 */}consttargetStack=[]// watcher棧export functionpushTarget(_target:?Watcher){...}/* 將watcher觀察者例項設定給Dep.target,用以依賴收集。同時將該例項存入target棧中 */export functionpopTarget(){...}/* 將觀察者例項從target棧中取出並設定給Dep.target */

這裡 Dep 的例項中的 subs 蒐集的依賴就是 watcher 了,它是 Watcher 的例項,將來用來通知更新

2.4 Watcher

12345678910111213141516171819202122232425262728293031323334353637383940 // src/core/observer/watcher.js/* 一個解析表示式,進行依賴收集的觀察者,同時在表示式資料變更時觸發回撥函式。它被用於$watch api以及指令 */exportdefaultclassWatcher{constructor(vm:Component,expOrFn:string|Function,cb:Function,options?:?Object,isRenderWatcher?:boolean// 是否是渲染watcher的標誌位){this.getter=expOrFn// 在get方法中執行if(this.computed){// 是否是 計算屬性this.value=undefinedthis.dep=newDep()// 計算屬性建立過程中並未求值}else{// 不是計算屬性會立刻求值this.value=this.get()}}/* 獲得getter的值並且重新進行依賴收集 */get(){pushTarget(this)// 設定Dep.target = thislet valuevalue=this.getter.call(vm,vm)popTarget()// 將觀察者例項從target棧中取出並設定給Dep.targetthis.cleanupDeps()returnvalue}addDep(dep:Dep){...}/* 新增一個依賴關係到Deps集合中 */

相關推薦

Vue原始碼閱讀--過濾器

過濾器 作用 : 用於一些常見的文字格式化 使用方式: 過濾器可以用在兩個地方:雙花括號插值和 v-bind 表示式 (後者從 2.1.0+ 開始支援)。過濾器應該被新增在 JavaScript 表示式的尾部,由“管道”符號指示: <!-- 在雙花括號中 --> {{ message |

vue 原始碼閱讀記錄

1. 入口>建構函式 >定義各類方法 > return vue; function Vue (options) { if ("development" !== 'production' && !(this instanceof Vue) ) {

Vue原始碼閱讀- 批量非同步更新與nextTick原理

vue已是目前國內前端web端三分天下之一,同時也作為本人主要技術棧之一,在日常使用中知其然也好奇著所以然,另外最近的社群湧現了一大票vue原始碼閱讀類的文章,在下借這個機會從大家的文章和討論中汲取了一些營養,同時對一些閱讀原始碼時的想法進行總結,出產一些文章,作為自己思考的總

Vue原始碼閱讀

vue已是目前國內前端web端三分天下之一,同時也作為本人主要技術棧之一,在日常使用中知其然也好奇著所以然,另外最近的社群湧現了一大票vue原始碼閱讀類的文章,在下借這個機會從大家的文章和討論中汲取了一些營養,同時對一些閱讀原始碼時的想法進行總結,出產一些文章,作為自己思考的總

【一套程式碼小程式&Native&Web階段總結篇】可以這樣閱讀Vue原始碼

前言 在實際程式碼過程中我們發現,我們可能又要做H5站又要做小程式同時還要做個APP,這裡會造成很大的資源浪費,如果設定一個規則,讓我們可以先寫H5程式碼,然後將小程式以及APP的業務差異程式碼做掉,豈不快哉?但小程式的web框架並不開源,不然也用不著我們在此費力了,經過研究,小程式web端框架是一套自

Mac下一款不錯的原始碼閱讀軟體

1、支援多語言:Ada, C, C++, C#, Java, FORTRAN, Delphi, Jovial, and PL/M ,混合語言的project也支援 2、多平臺: Windows/Linux/Solaris/HP-UX/IRIX/MAC OS X 3、程式碼語法高亮、程式碼折迭

Memcache-Java-Client-Release原始碼閱讀(之七)

一、主要內容 本章節的主要內容是介紹Memcache Client的Native,Old_Compat,New_Compat三個Hash演算法的應用及實現。 二、準備工作 1、伺服器啟動192.168.0.106:11211,192.168.0.106:11212兩個服務端例項。

Memcache-Java-Client-Release原始碼閱讀(之六)

一、主要內容 本章節的主要內容是介紹Memcache Client的一致性Hash演算法的應用及實現。 二、準備工作 1、伺服器啟動192.168.0.106:11211,192.168.0.106:11212兩個服務端例項。 2、示例程式碼: String[] serve

【筆記】ThreadPoolExecutor原始碼閱讀(三)

執行緒數量的維護 執行緒池的大小有兩個重要的引數,一個是corePoolSize(核心執行緒池大小),另一個是maximumPoolSize(最大執行緒大小)。執行緒池主要根據這兩個引數對執行緒池中執行緒的數量進行維護。 需要注意的是,執行緒池建立之初是沒有任何可用執行緒的。只有在有任務到達後,才開始建立

Java7、8中HashMap和ConcurrentHashMap原始碼閱讀

首先來看下HashMap的類繼承結構: public class HashMap extends AbstractMap<K,V> impement Map<K,V>,Coloneable,Serializable{ } 可以看出HashMap實現了Map介面。其裡面的方法都是

Promise原始碼閱讀之建構函式+then過程

前言 Promise是非同步程式設計的一種方案,ES6規範中將其寫入規範標準中,統一了用法。 考慮到瀏覽器的相容性,Vue專案中使用promise,就具體閱讀promise原始碼,看看內部的具體實現。 具體分析 通過具體例項來閱讀promise原始碼的實現,例項如下: new

vue-原始碼剖析-雙向繫結

專案中vue比較多,大概知道實現,最近翻了一下雙向繫結的程式碼,這裡寫一下閱讀後的理解。 專案目錄 拉到vue的程式碼之後,首先來看一下專案目錄,因為本文講的是雙向繫結,所以這裡主要看雙向繫結這塊的程式碼。 入口 從入口開始:src/core/index.js index

JDK原始碼閱讀:InterruptibleChannel與可中斷IO,ig牛逼

Java傳統IO是不支援中斷的,所以如果程式碼在read/write等操作阻塞的話,是無法被中斷的。這就無法和Thead的interrupt模型配合使用了。JavaNIO眾多的升級點中就包含了IO操作對中斷的支援。InterruptiableChannel表示支援中斷的Channel。我們常用的FileCha

zookeeper原始碼閱讀系列

1;github 下載 zookeeper原始碼 2:修改build.xml檔案和ivy.xml a:build.xml 將地址: get src=”http://downloads.sourceforge.net/project/ant-eclipse/ant-eclipse/1

vue原始碼探究---讀vue技術揭祕(1)

需要了解 RollUp Flow RollUp Rollup是一個js的模組打包器,可以將小塊程式碼編譯成大塊複雜程式碼,那為什麼這樣做呢,為了降低開發問題時候的複雜度 Tree-shaking(搖樹優化) 除了使用ES6的模組外,Rollup還能靜態分析程式碼中

vue原始碼學習——觀察者模式

情景:接觸過vue的同學都知道,我們曾經都很好奇為什麼vue能這麼方便的進行資料處理,當一個物件的某個狀態改變之後,只要依賴這個資料顯示的部分也會發生改變,如果你依舊很好奇,那麼今天你就可以瞭解一下實現的原理 什麼是觀察者模式​​​​​​​    官方解釋是

vue原始碼學習——資料雙向繫結的Object.defineProperty

情景:vue雙向繫結,這應該是多數講vue優勢脫口而出的名詞,然後你就會接觸到一個方法 Object.defineProperty(a,"b",{}) 這個方法該怎麼用 簡單例子敲一下 var a = {} Object.defineProperty(a,"b

Vue原始碼學習(二)——生命週期

官網對生命週期給出了一個比較完成的流程圖,如下所示: 從圖中我們可以看到我們的Vue建立的過程要經過以下的鉤子函式: beforeCreate =&gt; created =&gt; beforeMount =&gt; mounted =&gt; beforeUpda

## Zookeeper原始碼閱讀(六) Watcher

前言 好久沒有更新部落格了,最近這段時間過得很壓抑,終於開始踏上為換工作準備的正軌了,工作又真的很忙而且很瑣碎,讓自己有點煩惱,希望能早點結束這種狀態。 繼上次分析了ZK的ACL相關程式碼後,ZK裡非常重要的另一個特性就是Watcher機制了。其實在我看來,就ZK的使用而言,Watche機制是最核心的特性

Vue 原始碼分析之proxy代理

Vue 原始碼分析之proxy代理 當我們在使用Vue進行資料設定時,通常初始化格式為: let data = { age: 12, name: 'yang' } // 例項化Vue物件 let vm = new Vue({ data })