追求卓越一諾千金

藍藍設計__dvon,2011年成立|_-黄岩岛现状,主創清華團隊-艾克医院院长,專注軟件和互聯網ui設計開發|-158计划最新网站。擅長企業信息化管理_亿彩彩票是什么、監控-闵行莘庄公园、大數據軟件UIUE谘詢和設計開發服務--|310v大赢家。立足UI-|喜悦张海军,好好學習--_2019还能买足彩的app,天天進步_苗老表!


Vue的響應式原理(MVVM)深入解析

2019-6-6 釋然 前端及開發文章及欣賞


如果您想訂閱本博客內容-|wifi密码破解软件,每天自動發到您的郵箱中||132彩票官网注册, 請點這裏

1. 如何實現一個響應式對象
最近在看 Vue 的源碼|_|160彩票软件,其中最核心基礎的一塊就是 Observer/Watcher/Dep, 簡而言之就是-||证件妹,Vue 是如何攔截數據的讀寫_|l乐蜂网, 如果實現對應的監聽-|苏州十中吧,並且特定的監聽執行特定的回調或者渲染邏輯的|品堂色。總的可以拆成三大塊來說||-天国凤凰第二部全集。這一塊|_-金诗麦闪电瘦,主要說的是 Vue 是如何將一個 plain object 給處理成 reactive object 的_-|国际刑警电影,也就是|-009彩票网风控无法提现,Vue 是如何攔截攔截對象的 get/set 的

我們知道||微锐答题,用 Object.defineProperty 攔截數據的 get/set 是 vue 的核心邏輯之一__中国邮政网络培训学院职业鉴定中心。這裏我們先考慮一個最簡單的情況 一個 plain obj 的數據||金陵御沁园,經過你的程序之後|--重庆鸿恩寺儿童公园,使得這個 obj 變成 Reactive Obj (不考慮數組等因素__-战狼彩票全天实时计划,隻考慮最簡單的基礎數據類型__htc手机防盗,和對象):

如果這個 obj 的某個 key 被 get, 則打印出 get ${key} - ${val} 的信息 
如果這個 obj 的某個 key 被 set, 如果監測到這個 key 對應的 value 發生了變化--7788电视剧百度影音,則打印出 set ${key} - ${val} - ${newVal} 的信息||_广州上牌。 
對應的簡要代碼如下:

Observer.js

export class Observer {
  constructor(obj) {
    this.obj = obj;
    this.transform(obj);
  }
  // 將 obj 裏的所有層級的 key 都用 defineProperty 重新定義一遍, 使之 reactive 
  transform(obj) {
    const _this = this;
    for (let key in obj) {
      const value = obj[key];
      makeItReactive(obj, key, value);
    }
  }
}
function makeItReactive(obj, key, val) {
  // 如果某個 key 對應的 val 是 object, 則重新迭代該 val, 使之 reactive 
  if (isObject(val)) {
    const childObj = val;
    new Observer(childObj);
  }
  // 如果某個 key 對應的 val 不是 Object, 而是基礎類型--_对子莲,我們則對這個 key 進行 defineProperty 定義 
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: () => {
      console.info(`get ${key}-${val}`)
      return val;
    },
    set: (newVal) => {
      // 如果 newVal 和 val 相等_|一站到底在线游戏,則不做任何操作(不執行渲染邏輯)
      if (newVal === val) {
        return;
      }
      // 如果 newVal 和 val 不相等|-乔洋演唱会,且因為 newVal 為 Object, 所以先用 Observer迭代 newVal, 使之 reactive, 再用 newVal 替換掉 val, 再執行對應操作(渲染邏輯)
      else if (isObject(newVal)) {
        console.info(`set ${key} - ${val} - ${newVal} - newVal is Object`);
        new Observer(newVal);
        val = newVal;
      }
      // 如果 newVal 和 val 不相等-僵尸围城怎么做,且因為 newVal 為基礎類型, 所以用 newVal 替換掉 val, 再執行對應操作(渲染邏輯)
      else if (!isObject(newVal)) {
        console.info(`set ${key} - ${val} - ${newVal} - newVal is Basic Value`);
        val = newVal;
      }
    }
  })
}

function isObject(data) {
  if (typeof data === 'object' && data != 'null') {
    return true;
  }
  return false;
}

index.js

import { Observer } from './source/Observer.js';
// 聲明一個 obj,為 plain Object
const obj = {
  a: {
    aa: 1
  },
  b: 2,
}
// 將 obj 整體 reactive 化
new Observer(obj);
// 無輸出
obj.b = 2;
// set b - 2 - 3 - newVal is Basic Value
obj.b = 3;
// set b - 3 - [object Object] - newVal is Object
obj.b = {
  bb: 4
}
// get b-[object Object]
obj.b;
// get a-[object Object]
obj.a;
// get aa-1
obj.a.aa
// set aa - 1 - 3 - newVal is Basic Value
obj.a.aa = 3

這樣|-5320xm软件,我們就完成了 Vue 的第一個核心邏輯||步步惊情港台版, 成功把一個任意層級的 plain object 轉化成 reactive object

2. 如何實現一個 watcher
前麵講的是如何將 plain object 轉換成 reactive object. 接下來講一下__-111彩票应用,如何實現一個watcher.

實現的偽代碼應如下:

偽代碼

// 傳入 data 參數新建新建一個 vue 對象
const v = new Vue({
    data: {
        a:1,
        b:2,
    }
});
// watch data 裏麵某個 a 節點的變動了-_湖南移动梦网营业厅,如果變動_|仙剑5破解吧,則執行 cb
v.$watch('a',function(){
    console.info('the value of a has been changed !');
});

//  watch data 裏麵某個 b 節點的變動了_丧尸围城2绝密档案,如果變動|_青岛版江南style,則執行 cb
v.$watch('b',function(){
    console.info('the value of b has been changed !');
})

Vue.js

// 引入將上麵中實現的 Observer
import { Observer } from './Observer.js';
import { Watcher } from './Watcher.js';

export default class Vue {
  constructor(options) {
    // 在 this 上掛載一個公有變量 $options ,用來暫存所有參數
    this.$options = options
    // 聲明一個私有變量 _data ,用來暫存 data
    let data = this._data = this.$options.data
    // 在 this 上掛載所有 data 裏的 key 值|_|青岛海底世界门票, 這些 key 值對應的 get/set 都被代理到 this._data 上對應的同名 key 值
    Object.keys(data).forEach(key => this._proxy(key));
    // 將 this._data 進行 reactive 化
    new Observer(data, this)
  }
  // 對外暴露 $watch 的公有方法_|中央6,可以對某個 this._data 裏的 key 值創建一個 watcher 實例
  $watch(expOrFn, cb) {
    // 注意||阿玛拉王国锻造材料,每一個 watcher 的實例化都依賴於 Vue 的實例化對象, 即 this
    new Watcher(this, expOrFn, cb)
  }
  //  將 this.keyName 的某個 key 值的 get/set 代理到  this._data.keyName 的具體實現
  _proxy(key) {
    var self = this
    Object.defineProperty(self, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter() {
        return self._data[key]
      },
      set: function proxySetter(val) {
        self._data[key] = val
      }
    })
  }
}

Watch.js

// 引入Dep.js, 是什麼我們待會再說
import { Dep } from './Dep.js';

export class Watcher {
  constructor(vm, expOrFn, cb) {
    this.cb = cb;
    this.vm = vm;
    this.expOrFn = expOrFn;
    // 初始化 watcher 時, vm._data[this.expOrFn] 對應的 val
    this.value = this.get();
  }
  // 用於獲取當前 vm._data 對應的 key = expOrFn 對應的 val 值
  get() {
    Dep.target = this;
    const value = this.vm._data[this.expOrFn];
    Dep.target = null;
    return value;
  }
  // 每次 vm._data 裏對應的 expOrFn, 即 key 的 setter 被觸發|||5个人出名了,都會調用 watcher 裏對應的 update方法
  update() {
    this.run();
  }
  run() {
    // 這個 value 是 key 被 setter 調用之後的 newVal, 然後比較 this.value 和 newVal, 如果不相等-__掌上彩票234,則替換 this.value 為 newVal, 並執行傳入的cb.
    const value = this.get();
    if (value !== this.value) {
      this.value = value;
      this.cb.call(this.vm);
    }
  }
}

對於什麼是 Dep, 和 Watcher 裏的 update() 方法到底是在哪個時候被誰調用的|_12 8事件,後麵會說

3. 如何收集 watcher 的依賴
前麵我們講了 watcher 的大致實現-|-中兴v880官方刷机包,以及 Vue 代理 data 到 this 上的原理-助赢计划网址。現在我們就來梳理一下||-猪饲料排行,Observer/Watcher 之間的關係|花萼楼,來說明它們是如何調用的.

首先, 我們要來理解一下 watcher 實例的概念|-刘春天膏方网。實際上 Vue 的 v-model, v-bind , {{ mustache }}, computed, watcher 等等本質上是分別對 data 裏的某個 key 節點聲明了一個 watcher 實例.

<input v-model="abc">
<span>{{ abc }}</span>
<p :data-key="abc"></p>
...

const v = new Vue({
    data:{
        abc: 111,
    }
    computed:{
        cbd:function(){
            return `${this.abc} after computed`;
        }
    watch:{
        abc:function(val){
            console.info(`${val} after watch`)
        }
     }  
    }
})

這裏-_众赢彩票开户,Vue 一共聲明了 4 個 watcher 實例來監聽abc, 1個 watcher 實例來監聽 cbd. 如果 abc 的值被更改|_|电子邮箱号码大全,那麼 4 個 abc - watcher 的實例會執行自身對應的特定回調(比如重新渲染dom,或者是打印信息等等)

不過---港服dnf,Vue 是如何知道--_少年进化论成员,某個 key 對應了多少個 watcher, 而 key 對應的 value 發生變化後-|阳新县邮编,又是如何通知到這些 watcher 來執行對應的不同的回調的呢___198彩票平台号?

實際上更深層次的邏輯是:

在 Observer階段||丫鬟受罚,會為每個 key 都創建一個 dep 實例_||oppo803。並且|永盛彩票手机,如果該 key 被某個 watcher 實例 get, 把該 watcher 實例加入 dep 實例的隊列裏||-132彩票在线登录。如果該 key 被 set, 則通知該 key 對應的 dep 實例_|-众发娱乐登录下载安装, 然後 dep 實例會將依次通知隊列裏的 watcher 實例, 讓它們去執行自身的回調方法

dep 實例是收集該 key 所有 watcher 實例的地方.

watcher 實例用來監聽某個 key -|易赢彩票合法的吗,如果該 key 產生變化_-卓易彩票安装,便會執行 watcher 實例自身的回調 


相關代碼如下:

Dep.js

export class Dep {
  constructor() {
    this.subs = [];
  }
  // 將 watcher 實例置入隊列
  addSub(sub) {
    this.subs.push(sub);
  }
  // 通知隊列裏的所有 watcher 實例|爱唯侦察网址,告知該 key 的 對應的 val 被改變
  notify() {
    this.subs.forEach((sub, index, arr) => sub.update());
  }
}

// Dep 類的的某個靜態屬性-__jthysh,用於指向某個特定的 watcher 實例.
Dep.target = null
observer.js

import {Dep} from './dep'
function makeItReactive(obj, key, val) {
 var dep = new Dep()
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: () => {
    // 收集依賴-_-广西公需科目考试网! 如果該 key 被某個 watcher 實例依賴|-视觉卡盟,則將該 watcher 實例置入該 key 對應的 dep 實例裏
    if(Dep.target){
      dep.addSub(Dep.target)
    }
    return val
  },
  set: (newVal) => {
    if (newVal === val) {
      return;
    }
    else if (isObject(newVal)) {
      new Observer(newVal);
      val = newVal;
    // 通知 dep 實例, 該 key 被 set||神州行轻松卡c套餐,讓 dep 實例向所有收集到的該 key 的 watcher 實例發送通知
    dep.notify()
    }
    else if (!isObject(newVal)) {
      val = newVal;
    // 通知 dep 實例, 該 key 被 set-|-棱台体积公式,讓 dep 實例向所有收集到的該 key 的 watcher 發送通知
    dep.notify()
    }
  }
})
     }    

watcher.js

import { Dep } from './Dep.js';

export class Watcher {
  constructor(vm, expOrFn, cb) {
    this.cb = cb;
    this.vm = vm;
    this.expOrFn = expOrFn;
    this.value = this.get();
  }
  get() {
    // 在實例化某個 watcher 的時候|-毛衣外套编织花样,會將Dep類的靜態屬性 Dep.target 指向這個 watcher 實例
    Dep.target = this;
    // 在這一步 this.vm._data[this.expOrFn] 調用了 data 裏某個 key 的 getter, 然後 getter 判斷類的靜態屬性 Dep.target 不為null, 而為 watcher 的實例东方证券中签号查询, 從而把這個 watcher 實例添加到 這個 key 對應的 dep 實例裏_众盈彩票新三D是骗局吗。 巧妙_--致富彩票是骗局吗!
    const value = this.vm._data[this.expOrFn];
    // 重置類屬性 Dep.target 
    Dep.target = null;
    return value;
  }

  // 如果 data 裏的某個 key 的 setter 被調用-|永盛彩票网怎么注册,則 key 會通知到 該 key 對應的 dep 實例, 該Dep實例--裙地垫卫生巾, 該 dep 實例會調用所有 依賴於該 key 的 watcher 實例的 update 方法__汕尾台风。
  update() {
    this.run();
  }
  run() {
    const value = this.get();
    if (value !== this.value) {
    this.value = value;
    // 執行 cb 回調
    this.cb.call(this.vm);
    }
  }
}

總結|-093彩票计划:
至此-金丝蓉, Watcher, Observer , Dep 的關係全都梳理完成_-|雷克萨斯ex350。而這些也是 Vue 實現的核心邏輯之一|天津apec限行规定。再來簡單總結一下三者的關係-|178国际娱乐会员登录,其實是一個簡單的 觀察-訂閱 的設計模式, 簡單來說就是|-|俞威, 觀察者觀察數據狀態變化, 一旦數據發生變化_-英冠达,則會通知對應的訂閱者-168极速开奖结果,讓訂閱者執行對應的業務邏輯 _||快板网。我們熟知的事件機製__wetool,就是一種典型的觀察-訂閱的模式

Observer, 觀察者--锰钢属于,用來觀察數據源變化. 
Dep, 觀察者和訂閱者是典型的 一對多 的關係,所以這裏設計了一個依賴中心,來管理某個觀察者和所有這個觀察者對應的訂閱者的關係, 消息調度和依賴管理都靠它--|人妻x人妻。 
Watcher, 訂閱者_|-1216网彩可信吗,當某個觀察者觀察到數據發生變化的時候|--湖南基础教育资源网,這個變化經過消息調度中心_||众发娱乐属于赌博吗,最終會傳遞到所有該觀察者對應的訂閱者身上_长沙市消防支队,然後這些訂閱者分別執行自身的業務回調即可 
參考 
Vue源碼解讀-滴滴FED 
代碼參考
藍藍設計www.jwrumpff.com )是一家專注而深入的界麵設計公司__网上订票取票时间限制,為期望卓越的國內外企業提供卓越的UI界麵設計_|_李驰新浪博客、BS界麵設計 _|_娱乐天地怎么充值、 cs界麵設計 |-_易中彩票平台可靠吗、 ipad界麵設計 |千朋、 包裝設計 |_金华市婺城区地图、 圖標定製 _|_东莞酒店一条龙服务、 用戶體驗 |-易投彩票、交互設計-11选五任选7中奖率、 網站建設 __-永胜国际福彩靠谱么、平麵設計服務|非常了得 陈星光。

標簽: Vue的響應式原理(MVVM)深入解析 « 用棧判斷是否是回文字符串 | 騰訊設計師--swaymond:如何讓你的設計稿做到95%還原__青草色的你?»


訂閱Rss