你為什麼要使用前端框架Vue?

1.前端框架的根本意義

1.1 前端框架的好處

最開始學習前端框架的時候(我第一個框架是 React)並不理解框架能帶來什麼,只是因為大家都在用框架,最實際的一個用途就是所有企業幾乎都在用框架,不用框架就 out 了.

隨著使用的深入我逐漸理解到框架的好處:

  • 1.組件化: 其中以 React 的組件化最為徹底,甚至可以到函數級別的原子組件,高度的組件化可以是我們的工程易於維護、易於組合拓展。
  • 2.天然分層: JQuery 時代的代碼大部分情況下是麵條代碼,耦合嚴重,現代框架不管是 MVC、MVP還是MVVM 模式都能幫助我們進行分層,代碼解耦更易於讀寫。
  • 3.生態: 現在主流前端框架都自帶生態,不管是數據流管理架構還是 UI 庫都有成熟的解決方案。


你為什麼要使用前端框架Vue?


1.2 前端框架的根本意義

簡單來說,前端框架的根本意義是解決了UI 與狀態同步問題

在 Vue 中我們如果要在todos中添加一條,只需要app4.todos.push({ text: '新項目' }),這時由於 Vue 內置的響應式系統會自動幫我們進行 UI 與狀態的同步工作.




  1. {{ todo.text }}



var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: '學習 JavaScript' },
{ text: '學習 Vue' },
{ text: '整個牛項目' }
]
}
})
//在此我向大家推薦一個前端全棧開發交流圈:619586920 突破技術瓶頸,提升思維能力

如果我們用 JQuery 或者 JS 進行操作,免不了一大堆li.appendChild、document.createElement等 DOM 操作,我們需要一長串 DOM 操作保證狀態與 UI 的同步,其中一個環節出錯就會導致 BUG,手動操作的缺點如下:

  1. 頻繁操作 DOM 性能低下.
  2. 中間步驟過多,易產生 bug且不易維護,而且心智要求較高不利於開發效率

不管是 vue 的數據劫持、Angular 的髒檢測還是 React 的組件級 reRender都是幫助我們解決 ui 與狀態同步問題的利器。

這也解釋了Backbone作為前端框架鼻祖在之後落寞的原因,Backbone只是引入了 MVC 的思想,並沒有解決 View 與 Modal 同步的問題,相比於現代的三大框架直接操作 Modal 就可以同步 UI 的特性, Backbone 仍然與 JQuery 綁定,在 View 裡操作 Dom來達到同步 UI 的目的,這顯然是不符合現代前端框架設計要求的。


2.Vue 如何保證 UI 與狀態同步

UI 在 MVVM 中指的是 View,狀態在 MVVM 中指的是 Modal,而保證 View 和 Modal 同步的是 View-Modal。

Vue 通過一個響應式系統保證了View 與 Modal的同步,由於要兼容IE,Vue 選擇了 Object.defineProperty作為響應式系統的實現,但是如果不考慮 IE 用戶的話,Object.defineProperty並不是一個好的選擇target=https%3A%2F%2Fjuejin.im%2Fpost%2F5acd0c8a6fb9a028da7cdfaf)。

我們將用 Proxy 實現一個響應式系統。


你為什麼要使用前端框架Vue?


2.1 發佈訂閱中心

一個響應式系統離不開發布訂閱模式,因為我們需要一個 Dep保存訂閱者,並在 Observer 發生變化時通知保存在 Dep 中的訂閱者,讓訂閱者得知變化並更新視圖,這樣才能保證視圖與狀態的同步。

/**
* [subs description] 訂閱器,儲存訂閱者,通知訂閱者
* @type {Map}
*/
export default class Dep {
constructor() {
// 我們用 hash 儲存訂閱者
this.subs = new Map();
}
// 添加訂閱者
addSub(key, sub) {
// 取出鍵為 key 的訂閱者
const currentSub = this.subs.get(key);
// 如果能取出說明有相同的 key 的訂閱者已經存在,直接添加
if (currentSub) {
currentSub.add(sub);
} else {
// 用 Set 數據結構儲存,保證唯一值
this.subs.set(key, new Set([sub]));
}
}
// 通知
notify(key) {
// 觸發鍵為 key 的訂閱者們
if (this.subs.get(key)) {

this.subs.get(key).forEach(sub => {
sub.update();
});
}
}
}

2.2 監聽者的實現

我們在訂閱器 Dep 中實現了一個notify方法來通知相應的訂閱這們,然而notify方法到底什麼時候被觸發呢?

當然是當狀態發生變化時,即 MVVM 中的 Modal 變化時觸發通知,然而Dep 顯然無法得知 Modal 是否發生了變化,因此我們需要創建一個監聽者Observer來監聽 Modal, 當 Modal 發生變化的時候我們就執行通知操作。

vue 基於Object.defineProperty來實現了監聽者,我們用 Proxy 來實現監聽者.

與Object.defineProperty監聽屬性不同, Proxy 可以監聽(實際是代理)整個對象,因此就不需要遍歷對象的屬性依次監聽了,但是如果對象的屬性依然是個對象,那麼 Proxy 也無法監聽,所以我們實現了一個observify進行遞歸監聽即可。

/**

* [Observer description] 監聽器,監聽對象,觸發後通知訂閱

* @param {[type]} obj [description] 需要被監聽的對象

*/

const Observer = obj => {

const dep = new Dep();

return new Proxy(obj, {

get: function(target, key, receiver) {

// 如果訂閱者存在,直接添加訂閱

if (Dep.target) {

dep.addSub(key, Dep.target);

}

return Reflect.get(target, key, receiver);

},

set: function(target, key, value, receiver) {

// 如果對象值沒有變,那麼不觸發下面的操作直接返回

if (Reflect.get(receiver, key) === value) {

return;

}

const res = Reflect.set(target, key, observify(value), receiver);

// 當值被觸發更改的時候,觸發 Dep 的通知方法

dep.notify(key);

return res;

},

});

};

/**

* 將對象轉為監聽對象

* @param {*} obj 要監聽的對象

*/

export default function observify(obj) {

if (!isObject(obj)) {

return obj;

}

// 深度監聽

Object.keys(obj).forEach(key => {

obj[key] = observify(obj[key]);

});

return Observer(obj);

}

2.3 訂閱者的實現

我們目前已經解決了兩個問題,一個是如何得知 Modal 發生了改變(利用監聽者 Observer 監聽 Modal 對象),一個是如何收集訂閱者並通知其變化(利用訂閱器收集訂閱者,並用notify通知訂閱者)。

我們目前還差一個訂閱者(Watcher)

// 訂閱者

export default class Watcher {

constructor(vm, exp, cb) {

this.vm = vm; // vm 是 vue 的實例

this.exp = exp; // 被訂閱的數據

this.cb = cb; // 觸發更新後的回調

this.value = this.get(); // 獲取老數據

}

get() {

const exp = this.exp;

let value;

Dep.target = this;

if (typeof exp === 'function') {

value = exp.call(this.vm);

} else if (typeof exp === 'string') {

value = this.vm[exp];

}

Dep.target = null;

return value;

}

// 將訂閱者放入待更新隊列等待批量更新

update() {

pushQueue(this);

}

// 觸發真正的更新操作

run() {

const val = this.get(); // 獲取新數據

this.cb.call(this.vm, val, this.value);

this.value = val;

}

}

2.4 批量更新的實現

我們在上一節中實現了訂閱者( Watcher),但是其中的update方法是將訂閱者放入了一個待更新的隊列中,而不是直接觸發,原因如下:


你為什麼要使用前端框架Vue?


因此這個隊列需要做的是異步去重,因此我們用 Set作為數據結構儲存 Watcher 來去重,同時用Promise模擬異步更新。

// 創建異步更新隊列
let queue = new Set()
// 用Promise模擬nextTick
function nextTick(cb) {
Promise.resolve().then(cb)
}
// 執行刷新隊列
function flushQueue(args) {
queue.forEach(watcher => {
watcher.run()
})
// 清空
queue = new Set()
}
// 添加到隊列
export default function pushQueue(watcher) {
queue.add(watcher)
// 下一個循環調用
nextTick(flushQueue)
}

2.5 小結

我們梳理一下流程, 一個響應式系統是如何做到 UI(View)與狀態(Modal)同步的?

我們首先需要監聽 Modal, 本文中我們用 Proxy 來監聽了 Modal 對象,因此在 Modal 對象被修改的時候我們的 Observer 就可以得知。

我們得知Modal發生變化後如何通知 View 呢?要知道,一個 Modal 的改變可能觸發多個 UI 的更新,比如一個用戶的用戶名改變了,它的個人信息組件、通知組件等等組件中的用戶名都需要改變,對於這種情況我們很容易想到利用發佈訂閱模式來解決,我們需要一個訂閱器(Dep)來儲存訂閱者(Watcher),當監聽到 Modal 改變時,我們只需要通知相關的訂閱者進行更新即可。

那麼訂閱者來自哪裡呢?其實每一個組件實例對應著一個訂閱者(正因為一個組件實例對應一個訂閱者,才能利用 Dep 通知到相應組件,不然亂套了,通知訂閱者就相當於間接通知了組件)。

當訂閱者得知了具體變化後它會進行相應的更新,將更新體現在 UI(View)上,至此UI 與 Modal 的同步完成了。


3.響應式系統並不是全部

響應式系統雖然是 Vue 的核心概念,但是一個響應式系統並不夠.

響應式系統雖然得知了數據值的變化,但是當值不能完整映射 UI 時,我們依然需要進行組件級別的 reRender,這種情況並不高效,因此 Vue 在2.0版本引入了虛擬 DOM, 虛擬 DOM進行進一步的 diff 操作可以進行細粒度更高的操作,可以保證 reReander 的下限(保證不那麼慢)。

除此之外為了方便開發者,vue 內置了眾多的指令,因此我們還需要一個 vue 模板解析器.

結語

感謝您的觀看,如有不足之處,歡迎批評指正。

對前端的技術,前端全棧技術感興趣的同學關注我的頭條號,並在後臺私信發送關鍵字:“前端”即可獲取免費的前端開發攻城師學習資料

知識體系已整理好,歡迎免費領取。還有視頻分享可以免費獲取。關注我,可以獲得沒有的經驗哦!


分享到:


相關文章: