摘要:前言最近在學習框架的基本原理,看了一些技術博客以及一些對源碼的簡單實現,對數據代理數據劫持模板解析變異數組方法雙向綁定有了更深的理解。
前言
最近在學習vue框架的基本原理,看了一些技術博客以及一些對vue源碼的簡單實現,對數據代理、數據劫持、模板解析、變異數組方法、雙向綁定有了更深的理解。于是乎,嘗試著去實踐自己學到的知識,用vue的一些基本原理實現一個簡單的todo-list,完成對深度復雜對象的雙向綁定以及對數組的監聽,加深了對vue基本原理的印象。
學習鏈接github地址:todo-list
在線預覽: https://fatdong1.github.io/to...
前排感謝以下文章,對我理解vue的基本原理有很大的幫助!
剖析vue實現原理,自己動手實現mvvm by DMQ
對vue早期源碼的理解 by 梁少峰
實現效果 數據代理 1.簡單介紹數據代理正常情況下,我們都會把數據寫在data里面,如下面所示
var vm = new Vue({ el: "#app", data: { title: "hello world" } methods: { changeTitle: function () { this.title = "hello vue" } } }) console.log(vm.title) // "hello world" or "hello vue"
如果沒有數據代理,而我們又要修改data里面的title的話,methods里面的changeTitle只能這樣修改成this.data.title = "hello vue", 下面的console也只能改成console.log(vm.data.title),數據代理就是這樣的功能。
2. 實現原理通過遍歷data里面的屬性,將每個屬性通過object.defineProperty()設置getter和setter,將data里面的每個屬性都復制到與data同級的對象里。
(對應上面的示例代碼)
觸發這里的getter將會觸發data里面對應屬性的getter,觸發這里的setter將會觸發data里面對應屬性的setter,從而實現代理。實現代碼如下:
var self = this; // this為vue實例, 即vm Object.keys(this.data).forEach(function(key) { Object.defineProperty(this, key, { // this.title, 即vm.title enumerable: false, configurable: true, get: function getter () { return self.data[key]; //觸發對應data[key]的getter }, set: function setter (newVal) { self.data[key] = newVal; //觸發對應data[key]的setter } }); }
雙向綁定對object.defineProperty不熟悉的小伙伴可以在MDN的文檔(鏈接)學習一下
數據變動 ---> 視圖更新
視圖更新(input、textarea) --> 數據變動
視圖更新 --> 數據變動這個方向的綁定比較簡單,主要通過事件監聽來改變數據,比如input可以監聽input事件,一旦觸發input事件就改變data。下面主要來理解一下數據變動--->視圖更新這個方向的綁定。
1. 數據劫持不妨讓我們自己思考一下,如何實現數據變動,對應綁定數據的視圖就更新呢?
答案還是object.defineProperty,通過object.defineProperty遍歷設置this.data里面所有屬性,在每個屬性的setter里面去通知對應的回調函數,這里的回調函數包括dom視圖重新渲染的函數、使用$watch添加的回調函數等,這樣我們就通過object.defineProperty劫持了數據,當我們對數據重新賦值時,如this.title = "hello vue",就會觸發setter函數,從而觸發dom視圖重新渲染的函數,實現數據變動,對應視圖更新。
2. 發布-訂閱模式那么問題來了,我們如何在setter里面觸發所有綁定該數據的回調函數呢?
既然綁定該數據的回調函數不止一個,我們就把所有的回調函數放在一個數組里面,一旦觸發該數據的setter,就遍歷數組觸發里面所有的回調函數,我們把這些回調函數稱為訂閱者。數組最好就定義在setter函數的最近的上級作用域中,如下面實例代碼所示。
Object.keys(this.data).forEach(function(key) { var subs = []; // 在這里放置添加所有訂閱者的數組 Object.defineProperty(this.data, key, { // this.data.title enumerable: false, configurable: true, get: function getter () { console.log("訪問數據啦啦啦") return this.data[key]; //返回對應數據的值 }, set: function setter (newVal) { if (newVal === this.data[key]) { return; // 如果數據沒有變動,函數結束,不執行下面的代碼 } this.data[key] = newVal; //數據重新賦值 subs.forEach(function () { // 通知subs里面的所有的訂閱者 }) } }); }
那么問題又來了,怎么把綁定數據的所有回調函數放到一個數組里面呢?
我們可以在getter里面做做手腳,我們知道只要訪問數據就會觸發對應數據的getter,那我們可以先設置一個全局變量target,如果我們要在data里面title屬性添加一個訂閱者(changeTitle函數),我們可以先設置target = changeTitle,把changeTitle函數緩存在target中,然后訪問this.title去觸發title的getter,在getter里面把target這個全局變量的值添加到subs數組里面,添加完成后再把全局變量target設置為null,以便添加其他訂閱者。實例代碼如下:
Object.keys(this.data).forEach(function(key) { var subs = []; // 在這里放置添加所有訂閱者的數組 Object.defineProperty(this.data, key, { // this.data.title enumerable: false, configurable: true, get: function getter () { console.log("訪問數據啦啦啦") if (target) { subs.push(target); } return this.data[key]; //返回對應數據的值 }, set: function setter (newVal) { if (newVal === this.data[key]) { return; // 如果數據沒有變動,函數結束,不執行下面的代碼 } this.data[key] = newVal; //數據重新賦值 subs.forEach(function () { // 通知subs里面的所有的訂閱者 }) } }); }
上面的代碼為了方便理解都是通過簡化的,實際上我們把訂閱者寫成一個構造函數watcher,在實例化訂閱者的時候去訪問對應的數據,觸發相應的getter,詳細的代碼可以閱讀DMQ的自己動手實現MVVM
3. 模板解析通過上面的兩個步驟我們已經實現一旦數據變動,就會通知對應綁定數據的訂閱者,接下來我們來簡單介紹一個特殊的訂閱者,也就是視圖更新函數,幾乎每個數據都會添加對應的視圖更新函數,所以我們就來簡單了解一下視圖更新函數。
假如說有下面這一段代碼,我們怎么把它解析成對應的html呢?
{{title}}
先簡單介紹視圖更新函數的用途,
比如解析指令v-model="title",v-on:click="changeTitle",還有把{{title}}替換為對應的數據等。
回到上面那個問題,如何解析模板?我們只要去遍歷所有dom節點包括其子節點,
如果節點屬性含有v-model,視圖更新函數就為把input的value設置為title的值
如果節點為文本節點,視圖更新函數就為先用正則表達式取出大括號里面的值"title",再設置文本節點的值為data["title"]
如果節點屬性含有v-on:xxxx,視圖更新函數就為先用正則獲取事件類型為click,然后獲取該屬性的值為changeTitle,則事件的回調函數為this.methods["changeTitle"],接著用addEventListener監聽節點click事件。
我們要知道視圖更新函數也是data對應屬性的訂閱者,如果不知道如何觸發視圖更新函數,可以把上面的發布-訂閱模式再看一遍。
可能有的小伙伴可能還有個疑問,如何實現input節點的值變化后,下面的h1節點的title值也發生變化?在遍歷所有節點后,如果節點含有屬性v-model,就用addEventListener監聽input事件,一旦觸發input事件,改變data["title"]的值,就會觸發title的setter,從而通知所有的訂閱者。
監聽數組變化 無法監控每個數組元素如果讓我們自己實現監聽數組的變化,我們可能會想到用object.defineProperty去遍歷數組每個元素并設置setter,但是vue源碼里面卻不是這樣寫的,因為對每一個數組元素defineProperty帶來代碼本身的復雜度增加和代碼執行效率的降低。
變異數組方法感謝Ma63d這篇文章下面的的評論,對此解釋得很詳細,這里也就不再贅述。
既然無法通過defineProperty監控數組的每個元素,我們可以重寫數組的方法(push, pop, shift, unshift, splice, sort, reverse)來改變數組。
vue文檔中是這樣寫的:
Vue 包含一組觀察數組的變異方法,所以它們也將會觸發視圖更新。這些方法如下:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
下面是 vue早期源碼學習系列之二:如何監聽一個數組的變化 中的實例代碼
const aryMethods = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"]; const arrayAugmentations = []; aryMethods.forEach((method)=> { // 這里是原生Array的原型方法 let original = Array.prototype[method]; // 將push, pop等封裝好的方法定義在對象arrayAugmentations的屬性上 // 注意:是屬性而非原型屬性 arrayAugmentations[method] = function () { console.log("我被改變啦!"); // 調用對應的原生方法并返回結果 return original.apply(this, arguments); }; }); let list = ["a", "b", "c"]; // 將我們要監聽的數組的原型指針指向上面定義的空數組對象 // 別忘了這個空數組的屬性上定義了我們封裝好的push等方法 list.__proto__ = arrayAugmentations; list.push("d"); // 我被改變啦! 4 // 這里的list2沒有被重新定義原型指針,所以就正常輸出 let list2 = ["a", "b", "c"]; list2.push("d"); // 4
對__proto__不熟悉的小伙伴可以去看一下王福明的博客,寫的很好。
變異數組方法的缺陷vue文檔中變異數組方法的缺陷
由于 JavaScript 的限制, Vue 不能檢測以下變動的數組:
當你利用索引直接設置一個項時,例如: vm.items[indexOfItem] = newValue
當你修改數組的長度時,例如: vm.items.length = newLength
同時文檔中也介紹了如何解決上面這兩個問題。
最后以上是自己對vue一些基本原理的理解,當然還有很多不足的地方,歡迎指正。本來自己也是為了應付面試才去學習vue框架的基本原理,但是簡單學習了這些vue基本的原理后,讓我明白通過深入學習框架原理,可以有效避開一些自己以后會遇到的坑,所以,有時間的話自己以后還是會去看看框架的基本原理。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84621.html
摘要:項目問題總結這個項目,很簡單,前端使用,后端使用進行開發。方便移動端開發。當動畫結束后,有一個鉤子函數可以使用其他一些功能組件,都是自己嘗試去編寫的,像日歷組件組件組件等。版本的,是沒有任何的鉤子函數,我就感覺懵逼了。。。 todo-list 項目問題總結 這個 todo-list 項目,很簡單,前端使用 react,后端 nodejs 使用 koa2 進行開發。數據庫使用 Mysql...
摘要:最近在研究的相關知識,最好的學習方法莫過于自己開發一個,這樣帶著問題來學習,進步自然飛速。在首頁里,我們會用寫一個導航,通過的路由導航到不同的應用。我們在文件夾里創建一個新的組件。 最近在研究vue的相關知識,最好的學習方法莫過于自己開發一個SPA,這樣帶著問題來學習,進步自然飛速。于是邊查邊寫差不多花了2周寫完了一個todo-list,功能不夠完備,但是麻雀雖小,卻也是五臟俱全,基本...
摘要:原文博客地址如何理解如何實現是否解讀過的源碼與框架的區別實現實現獨立初始化實例兩者的區別數據和視圖的分離,解耦開放封閉原則,對擴展開放,對修改封閉在中在代碼中操作視圖和數據,混在一塊了以數據驅動視圖,只關心數據變化, 原文博客地址:https://finget.github.io/2018/05/31/mvvm-vue/ MVVM 如何理解 MVVM 如何實現 MVVM 是否解讀過 ...
摘要:因為其組件只是根據提供的及屬性,生成動畫的數據,業務應用中拿到生成的數據后根據需要添加需要動畫的組件樣式。除了上述簡單的動畫應用,在復雜動畫的實現方面,表現非常優越。 WEB應用中動畫很重要 不管是web應用還是原生應用,也不論是PC端應用還是移動端應用,動畫都扮演了一個重要的角色。 盡管動畫并不會添加應用的實際動能,但一個好的動畫,一個流暢且優雅,選擇在恰當時機出現的動畫,能為應用增...
摘要:前端日報精選譯測試版本漫談前端體系建設輕松理解框架的基本原理,簡單實現一個關鍵請求為什么是而不是的中文譯擴展知乎專欄譯白話掘金調查報告眾成翻譯的平凡之路學習人氣眼中的效果中掘金與阻止元素被選中及清除選中的方法總結風 2017-08-04 前端日報 精選 【譯】React 16 測試版本漫談前端體系建設輕松理解vue框架的基本原理,簡單實現一個todo-list關鍵請求為什么是displ...
閱讀 2878·2021-09-22 15:54
閱讀 1886·2019-08-30 15:53
閱讀 2239·2019-08-29 16:33
閱讀 1416·2019-08-29 12:29
閱讀 1386·2019-08-26 11:41
閱讀 2366·2019-08-26 11:34
閱讀 2947·2019-08-23 16:12
閱讀 1420·2019-08-23 15:56