你爲何要使用前端框架Vue?

1.前端框架的根本意義前端

1.1 前端框架的好處vue

最開始學習前端框架的時候(我第一個框架是 React)並不理解框架能帶來什麼,只是由於你們都在用框架,最實際的一個用途就是全部企業幾乎都在用框架,不用框架就 out 了.前端框架

隨着使用的深刻我逐漸理解到框架的好處:數據結構

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

1.2 前端框架的根本意義架構

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

在 Vue 中咱們若是要在todos中添加一條,只須要app4.todos.push({ text: '新項目' }),這時因爲 Vue 內置的響應式系統會自動幫咱們進行 UI 與狀態的同步工做.框架

<div id="app-4">
  <ol>
    <li v-for="todo in todos">
      {{ todo.text }}
    </li>
  </ol>
</div>
var app4 = new Vue({
  el: '#app-4',
  data: {
    todos: [
      { text: '學習 JavaScript' },
      { text: '學習 Vue' },
      { text: '整個牛項目' }
    ]
  }
})
//在此我向你們推薦一個前端全棧開發交流圈:619586920 突破技術瓶頸,提高思惟能力

若是咱們用 JQuery 或者 JS 進行操做,免不了一大堆li.appendChilddocument.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 實現一個響應式系統。

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方法是將訂閱者放入了一個待更新的隊列中,而不是直接觸發,緣由以下:

所以這個隊列須要作的是異步去重,所以咱們用 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 模板解析器.

相關文章
相關標籤/搜索