您好,登錄后才能下訂單哦!
前言
最近在學(xué)習(xí)vue框架的基本原理,看了一些技術(shù)博客以及一些對vue源碼的簡單實現(xiàn),對數(shù)據(jù)代理、數(shù)據(jù)劫持、模板解析、變異數(shù)組方法、雙向綁定有了更深的理解。于是乎,嘗試著去實踐自己學(xué)到的知識,用vue的一些基本原理實現(xiàn)一個簡單的todo-list,完成對深度復(fù)雜對象的雙向綁定以及對數(shù)組的監(jiān)聽,加深了對vue基本原理的印象。
github地址:todo-list
學(xué)習(xí)鏈接
前排感謝以下文章,對我理解vue的基本原理有很大的幫助!
剖析vue實現(xiàn)原理,自己動手實現(xiàn)mvvm by DMQ
對vue早期源碼的理解 by 梁少峰
實現(xiàn)效果
數(shù)據(jù)代理
1.簡單介紹數(shù)據(jù)代理
正常情況下,我們都會把數(shù)據(jù)寫在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'
如果沒有數(shù)據(jù)代理,而我們又要修改data里面的title的話,methods里面的changeTitle只能這樣修改成this.data.title = 'hello vue'
, 下面的console也只能改成console.log(vm.data.title),
數(shù)據(jù)代理就是這樣的功能。
2. 實現(xiàn)原理
通過遍歷data里面的屬性,將每個屬性通過object.defineProperty()設(shè)置getter和setter,將data里面的每個屬性都復(fù)制到與data同級的對象里。
(對應(yīng)上面的示例代碼)
觸發(fā)這里的getter將會觸發(fā)data里面對應(yīng)屬性的getter,觸發(fā)這里的setter將會觸發(fā)data里面對應(yīng)屬性的setter,從而實現(xiàn)代理。實現(xiàn)代碼如下:
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]; //觸發(fā)對應(yīng)data[key]的getter }, set: function setter (newVal) { self.data[key] = newVal; //觸發(fā)對應(yīng)data[key]的setter } }); }
對object.defineProperty不熟悉的小伙伴可以在MDN的文檔(鏈接)學(xué)習(xí)一下
雙向綁定
視圖更新 --> 數(shù)據(jù)變動
這個方向的綁定比較簡單,主要通過事件監(jiān)聽來改變數(shù)據(jù),比如input可以監(jiān)聽input事件,一旦觸發(fā)input事件就改變data。下面主要來理解一下數(shù)據(jù)變動--->視圖更新
這個方向的綁定。
1. 數(shù)據(jù)劫持
不妨讓我們自己思考一下,如何實現(xiàn)數(shù)據(jù)變動,對應(yīng)綁定數(shù)據(jù)的視圖就更新呢?
答案還是object.defineProperty,通過object.defineProperty遍歷設(shè)置this.data里面所有屬性,在每個屬性的setter里面去通知對應(yīng)的回調(diào)函數(shù),這里的回調(diào)函數(shù)包括dom視圖重新渲染的函數(shù)、使用$watch添加的回調(diào)函數(shù)等,這樣我們就通過object.defineProperty劫持了數(shù)據(jù),當(dāng)我們對數(shù)據(jù)重新賦值時,如this.title = 'hello vue'
,就會觸發(fā)setter函數(shù),從而觸發(fā)dom視圖重新渲染的函數(shù),實現(xiàn)數(shù)據(jù)變動,對應(yīng)視圖更新。
2. 發(fā)布-訂閱模式
那么問題來了,我們?nèi)绾卧趕etter里面觸發(fā)所有綁定該數(shù)據(jù)的回調(diào)函數(shù)呢?
既然綁定該數(shù)據(jù)的回調(diào)函數(shù)不止一個,我們就把所有的回調(diào)函數(shù)放在一個數(shù)組里面,一旦觸發(fā)該數(shù)據(jù)的setter,就遍歷數(shù)組觸發(fā)里面所有的回調(diào)函數(shù),我們把這些回調(diào)函數(shù)稱為訂閱者。數(shù)組最好就定義在setter函數(shù)的最近的上級作用域中,如下面實例代碼所示。
Object.keys(this.data).forEach(function(key) { var subs = []; // 在這里放置添加所有訂閱者的數(shù)組 Object.defineProperty(this.data, key, { // this.data.title enumerable: false, configurable: true, get: function getter () { console.log('訪問數(shù)據(jù)啦啦啦') return this.data[key]; //返回對應(yīng)數(shù)據(jù)的值 }, set: function setter (newVal) { if (newVal === this.data[key]) { return; // 如果數(shù)據(jù)沒有變動,函數(shù)結(jié)束,不執(zhí)行下面的代碼 } this.data[key] = newVal; //數(shù)據(jù)重新賦值 subs.forEach(function () { // 通知subs里面的所有的訂閱者 }) } }); }
那么問題又來了,怎么把綁定數(shù)據(jù)的所有回調(diào)函數(shù)放到一個數(shù)組里面呢?
我們可以在getter里面做做手腳,我們知道只要訪問數(shù)據(jù)就會觸發(fā)對應(yīng)數(shù)據(jù)的getter,那我們可以先設(shè)置一個全局變量target,如果我們要在data里面title屬性添加一個訂閱者(changeTitle函數(shù)),我們可以先設(shè)置target = changeTitle,把changeTitle函數(shù)緩存在target中,然后訪問this.title去觸發(fā)title的getter,在getter里面把target這個全局變量的值添加到subs數(shù)組里面,添加完成后再把全局變量target設(shè)置為null,以便添加其他訂閱者。實例代碼如下:
Object.keys(this.data).forEach(function(key) { var subs = []; // 在這里放置添加所有訂閱者的數(shù)組 Object.defineProperty(this.data, key, { // this.data.title enumerable: false, configurable: true, get: function getter () { console.log('訪問數(shù)據(jù)啦啦啦') if (target) { subs.push(target); } return this.data[key]; //返回對應(yīng)數(shù)據(jù)的值 }, set: function setter (newVal) { if (newVal === this.data[key]) { return; // 如果數(shù)據(jù)沒有變動,函數(shù)結(jié)束,不執(zhí)行下面的代碼 } this.data[key] = newVal; //數(shù)據(jù)重新賦值 subs.forEach(function () { // 通知subs里面的所有的訂閱者 }) } }); }
上面的代碼為了方便理解都是通過簡化的,實際上我們把訂閱者寫成一個構(gòu)造函數(shù)watcher,在實例化訂閱者的時候去訪問對應(yīng)的數(shù)據(jù),觸發(fā)相應(yīng)的getter,詳細的代碼可以閱讀DMQ的自己動手實現(xiàn)MVVM
3. 模板解析
通過上面的兩個步驟我們已經(jīng)實現(xiàn)一旦數(shù)據(jù)變動,就會通知對應(yīng)綁定數(shù)據(jù)的訂閱者,接下來我們來簡單介紹一個特殊的訂閱者,也就是視圖更新函數(shù),幾乎每個數(shù)據(jù)都會添加對應(yīng)的視圖更新函數(shù),所以我們就來簡單了解一下視圖更新函數(shù)。
假如說有下面這一段代碼,我們怎么把它解析成對應(yīng)的html呢?
<input v-model="title"> <h2>{{title}}</h2> <button v-on:click="changeTitle">change title<button>
先簡單介紹視圖更新函數(shù)的用途,
比如解析指令v-model="title",v-on:click="changeTitle"
,還有把{{title}}替換為對應(yīng)的數(shù)據(jù)等。
回到上面那個問題,如何解析模板?我們只要去遍歷所有dom節(jié)點包括其子節(jié)點,
我們要知道視圖更新函數(shù)也是data對應(yīng)屬性的訂閱者,如果不知道如何觸發(fā)視圖更新函數(shù),可以把上面的發(fā)布-訂閱模式再看一遍。
可能有的小伙伴可能還有個疑問,如何實現(xiàn)input節(jié)點的值變化后,下面的h2節(jié)點的title值也發(fā)生變化?在遍歷所有節(jié)點后,如果節(jié)點含有屬性v-model,就用addEventListener監(jiān)聽input事件,一旦觸發(fā)input事件,改變data['title']的值,就會觸發(fā)title的setter,從而通知所有的訂閱者。
監(jiān)聽數(shù)組變化
無法監(jiān)控每個數(shù)組元素
如果讓我們自己實現(xiàn)監(jiān)聽數(shù)組的變化,我們可能會想到用object.defineProperty去遍歷數(shù)組每個元素并設(shè)置setter,但是vue源碼里面卻不是這樣寫的,因為對每一個數(shù)組元素defineProperty帶來代碼本身的復(fù)雜度增加和代碼執(zhí)行效率的降低。
變異數(shù)組方法
既然無法通過defineProperty監(jiān)控數(shù)組的每個元素,我們可以重寫數(shù)組的方法(push, pop, shift, unshift, splice, sort, reverse)來改變數(shù)組。
vue文檔中是這樣寫的:
Vue 包含一組觀察數(shù)組的變異方法,所以它們也將會觸發(fā)視圖更新。這些方法如下:
下面是vue早期源碼學(xué)習(xí)系列之二:如何監(jiān)聽一個數(shù)組的變化 中的實例代碼
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('我被改變啦!'); // 調(diào)用對應(yīng)的原生方法并返回結(jié)果 return original.apply(this, arguments); }; }); let list = ['a', 'b', 'c']; // 將我們要監(jiān)聽的數(shù)組的原型指針指向上面定義的空數(shù)組對象 // 別忘了這個空數(shù)組的屬性上定義了我們封裝好的push等方法 list.__proto__ = arrayAugmentations; list.push('d'); // 我被改變啦! 4 // 這里的list2沒有被重新定義原型指針,所以就正常輸出 let list2 = ['a', 'b', 'c']; list2.push('d'); // 4
變異數(shù)組方法的缺陷
vue文檔中變異數(shù)組方法的缺陷
由于 JavaScript 的限制, Vue 不能檢測以下變動的數(shù)組:
同時文檔中也介紹了如何解決上面這兩個問題。
最后
以上是自己對vue一些基本原理的理解,當(dāng)然還有很多不足的地方,歡迎指正。本來自己也是為了應(yīng)付面試才去學(xué)習(xí)vue框架的基本原理,但是簡單學(xué)習(xí)了這些vue基本的原理后,讓我明白通過深入學(xué)習(xí)框架原理,可以有效避開一些自己以后會遇到的坑,所以,有時間的話自己以后還是會去看看框架的基本原理。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。