一、前言
最近一直在使用vue做項目,閑暇之余查閱了一些關於vue實現原理的資料,一方面對所了解到的知識做個總結,另外一方面希望能對看到此文章的同學有所幫助。本文如有不足之處,還請過往的大佬批評指正。
二、vue實現原理概述
vue作為一個前端漸進式的MVVM開發庫,將廣大的前端勞苦大眾從DOM操作中解放出來;說到vue的實現原理,大體可分為三個要素:
1、數據的響應式,即vue可以監聽到數據的變化
2、模板引擎,模板引擎大家都應該不陌生,同之前使用的handlebars、artTemplate相似,都類似於Html語法,不過可以寫一些邏輯在上面(數據綁定和事件綁定)
3、Html的渲染,即通過模板引擎將數據渲染到頁面過程
這三點為理論上的實現原理,不過使用vue寫項目的時候,從數據變化到頁面展示的變化大體可分為一下一個步驟:
第一步:解析模板成render函數
第二步:響應式開始監聽數據的變化
第三步:首次渲染頁面,顯示頁面,並綁定數據依賴和和事件
第四步:data的變化,觸發rerender,更新視圖的顯示
一下將通過解釋vue監聽數據的變化、模板綁定數據、數據渲染至頁面、實現只渲染數據改變部分DOM這幾個部分進行講解
三、vue實現整體解析
1、vue如何實現對數據的監聽的?
說到vue對數據的監聽,不得不提到Object.defineProperty,當前主流瀏覽器均支持此屬性,vue的數據雙向綁定及數據的監聽都是基於此實現的;請看下面代碼:
1 var obj = {};
2 var name = "MrGao"
3 Object.defineProperty(obj, "name", { 4 get: function() { // 獲取屬性值 5 return name; // name的初始值為"MrGao" 6 }, 7 set: function(newval) { // set新的值給屬性 8 name = newval 9 } 10 }) 11 console.log(obj.name) // MrGao 12 obj.name = "MrBone"; 13 console.log(obj.name) // MrBone
可以看到Object.defineProperty傳了三個參數進去
第一個參數為目標對象
第二個參數為要定義的屬性或方法名稱,(如果對象中不包含此屬性將此屬性添加到目標對象里,vue中將data數據指向vm就是用的這里,下面將詳細講解)
第三個參數為目標屬性的所擁有的特性
這三個參數為必填參數,另外對於第三個參數除get和set之外還有一些其它的屬性,在這里提一下
value: 屬性的值
writable:屬性的值是否能被重寫,當設置為false的時候為只讀,不能通過set進行重新賦值
configurable:是否可以設置他的其他屬性(value,writable)
enumerable:是否可以在Object.keys或for...in中列舉出來
get和set上面例子已經提到
Object.defineProperty在vue中的實現
我們知道在寫vue的時候都是可以通過this.*來讀取改變在data中定義的屬性,那么這是怎么實現的呢?這就是通過Object.defineProperty將data中的屬性指向到vm中,即this就是之vm實例,參考一下實現代碼:
1 var data = {
2 name: "MrGao", 3 age: 22, 4 address: "HangZhou" 5 }; 6 var vm = {}; 7 8 for(key in data) { 9 (function(key){ // 采用閉包,保證key的獨立作用域 10 Object.defineProperty(vm, key, { 11 get: function() { 12 return data[key] 13 }, 14 set: function(newval) { 15 data[key] = newval 16 } 17 }) 18 })(key) 19 } 20 21 console.log(vm.name); 22 vm.name = "MrBone"; 23 console.log(vm.name);
通過分析上面代碼可以看出vue通過Object.defineProperty將data中的屬性指向vm實例,即this可以獲取data中的屬性
以上為vue數據響應式監聽的原理,接下來我們來看一下模板引擎的實現
2、vue模板引擎
說模板之前先來看下模板是什么:
本質:字符串
帶邏輯: 如v-if、v-for、v-model
特點:最終要通過js轉換成html代碼來顯示在頁面上
所以如果要讓有邏輯的模板顯示在頁面上,就需要通過js聲明一個函數來處理這件事情,我們給它起個名字叫做render
通過都vue源碼,可獲取關於render函數中的一些核心函數,下面通過一個簡單的例子來做下介紹:

通過以上代碼可看出vue中render函數有兩個特點,一是使用了with函數,二是模板中所有的信息都包含在render函數中;其中函數中的this指的就是vm,所以_c、_v、_s、tittle分別指的是vm._c、vm._v、vm._s、vm.tittle。這里重點說下_c,這里的_c和snabbdom.js中的h()函數相似,下面還會說到vdom中的patch()函數,要了解虛擬DOM相關細節我將在下一篇博客中介紹。
此處render函數將返回一個虛擬DOM,即vnode,並將vnode傳到patch()函數中;參考下面代碼:
vm._updata(vnode) {
const prevVnode = vm._vnode; vm._vnode = vnode; // 將新的vnode賦值給舊的_vnode if (!prevVnode) { // 如果舊的_vnode不存在則將dom掛載在vm.$el vm.$el = vm.__patch__(vm.$el, vnode); } else { vm.$el = vm.__patch__(prevVnode, vnode); } } function updateComponent() { // vm._render為返回的虛擬DOM vm._update(vm._render()); }
3、數據渲染至頁面
將數據渲染進頁面,通過patch函數將虛擬DOM轉換為真實DOM結構頁面進行展示,渲染頁面的函數調用如下:
初次渲染
1、執行updateComponent,執行vm._render()
2、執行render函數,會訪問到數據源
3、相應式get方法監聽到訪問的數據源執行updateComponent,到patch()方法中的 vm.$el = vm.__patch__(vm.$el, vnode);
4、patch()將虛擬DOM渲染成DOM,初次渲染完成
修改屬性渲染
1、修改屬性,被響應式set監聽到
2、set中執行updateComponent
3、updateComponent重新執行vm._render()
4、生成vnode和舊的prevVnode,通過patch進行比較 vm.$el = vm.__patch__(prevVnode, vnode);
5、DOM更新
四、小節
以上為今天所講的內容,對vue的實現原理進行了一個簡單剖析,至於vue中實現的DOM的差量更新,vue2.0之后引入的虛擬DOM,是基於vdom的diff算法原理,通過patch函數比較vdom更新前后的數據差異進行DOM的更新,篇幅有限,這里就不在對虛擬DOM進行贅述,如有興趣,歡迎閱讀我下一篇關於虛擬DOM的文章。
