1. 程式人生 > >vue2.x響應式原理,vue與react響應式簡單對比

vue2.x響應式原理,vue與react響應式簡單對比

配合ppt食用更佳ppt

實現的最終目標

const demo = new Vue({
  data: {
    text: "before",
  },
  // 對應的template 為 <div><span>{{text}}</span></div>
  render(h){
    return h('div', {}, [
      h('span', {}, [this.__toString__(this.text)])
    ])
  }
})
setTimeout(function(){
  demo.text = "after"
}, 3000)

對應的虛擬DOM會從

<div><span>before</span></div> 

變成

 <div><span>after</span></div>

第一步,監聽data下邊的所有屬性,轉換為響應式

思路

  • 當data下的某個屬性變化時,如何觸發相應的函式?

方案:ES5中新添加了一個方法:Object.defineProperty,通過這個方法,可以自定義gettersetter函式,那麼在獲取物件屬性或者設定物件屬性時就能夠執行相應的回撥函式

Object.defineProperty MDN

程式碼如下:

class Vue {
  constructor(options) {
    this.$options = options
    this._data = options.data
    observer(options.data, this._update.bind(this))
    this._update()
  }
  _update(){
    this.$options.render()
  }
}

function observer(obj, cb) {
  Object.keys(obj).forEach((key) => {
    defineReactive(obj, key, obj[key], cb)
  })
}

function
defineReactive(obj, key, val, cb) {
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { console.log('你訪問了' + key) return val }, set: newVal => { if (newVal === val) return console.log('你設定了' + key) console.log('新的' + key + ' = ' + newVal) val = newVal cb() } }) } var demo1 = new Vue({ el: '#demo', data: { text: "before" }, render(){ console.log("我要render了") } })
  • 引發了第二個問題,如果data中的屬性是一個物件還能觸發我們的回掉函式麼?比如說下邊的demo
var demo2 = new Vue({
  el: '#demo',
  data: {
    text: "before",
    o: {
      text: "o-before"
    }
  },
  render(){
    console.log("我要render了")
  }
})

方案:用遞迴完善上邊的響應式,需要在它開始對屬性進行響應式轉換的時候,前邊加個判斷,即如下

function observer(obj) {
  Object.keys(obj).forEach((key) => {
    if (typeof obj[key] === 'object') {
      new observer(obj[key], cb)
    }
    defineReactive(obj, key, obj[key])
  })
}
  • 實際寫的過程中發現呼叫data的屬性時需要這樣寫demo._data.text,肯定是沒有demo.text這樣寫來的方便,所以就需要加一層代理進行轉換

程式碼如下:

  _proxy(key) {
    const 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
      }
    })
  }

然後在建構函式中加上這麼一句話

Object.keys(options.data).forEach(key => this._proxy(key))

到此,我們的data屬性已經變為響應式的了,只要data的屬性發生變化,那麼就會觸發render函式。這也是為什麼只有vue元件中的data屬性才是響應式的,其他地方宣告的值均不是響應式的原因。但是這裡有個問題,即觸發render函式的準確度問題!

第二步,解決準確度問題,引出虛擬dom

比如下邊的demo

new Vue({
  template: `
    <div>
      <span>name:</span> {{name}}
    <div>`,
  data: {
    name: 'js',
    age: 24
  }
})

setTimeout(function(){
  demo.age = 25
}, 3000)

template中只用到了data中的name屬性,但是當修改age屬性的時候,會不會觸發渲染呢?答案是:會。但實際是不需要觸發渲染機制的

解決這個問題,先要簡單說下虛擬dom。vue有兩種寫法:

// template模板寫法(最常用的)
new Vue({
  data: {
    text: "before",
  },
  template: `
    <div>
      <span>text:</span> {{text}}
    </div>`
})

// render函式寫法,類似react的jsx寫法
new Vue({
  data: {
    text: "before",
  },
  render (h) {
    return (
      <div>
        <span>text:</span> {{text}}
      </div>
    )
  }
})

由於vue2.x引入了虛擬dom的原因,這兩種寫法最終都會被解析成虛擬dom,但在這之前,他們會先被解析函式轉換成同一種表達方式,即如下:

new Vue({
  data: {
    text: "before",
  },
  render(){
    return this.__h__('div', {}, [
      this.__h__('span', {}, [this.__toString__(this.text)])
    ])
  }
})

透過上邊的render函式中的this.__h__方法,可以簡單瞭解下虛擬dom

function VNode(tag, data, children, text) {
  return {
    tag: tag, // html標籤名
    data: data, // 包含諸如 class 和 style 這些標籤上的屬性
    children: children, // 子節點
    text: text // 文字節點
  }
}

寫一個簡單的虛擬dom:

function VNode(tag, data, children, text) {
  return {
    tag: tag,
    data: data,
    children: children,
    text: text
  }
}

class Vue {
  constructor(options) {
    this.$options = options
    const vdom = this._update()
    console.log(vdom)
  }
  _update() {
    return this._render.call(this)
  }
  _render() {
    const vnode = this.$options.render.call(this)
    return vnode
  }
  __h__(tag, attr, children) {
    return VNode(tag, attr, children.map((child)=>{
      if(typeof child === 'string'){
        return VNode(undefined, undefined, undefined, child)
      }else{
        return child
      }
    }))
  }
  __toString__(val) {
    return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val);
  }
}

var demo = new Vue({
  el: '#demo',
  data: {
    text: "before",
  },
  render(){
    return this.__h__('div', {}, [
      this.__h__('span', {}, [this.__toString__(this.text)])
    ])
  }
})

回頭看問題,也就是說,我需要知道render函式中依賴了data中的哪些屬性,只有這些屬性變化,才需要去觸發render函式

第三步,依賴收集,準確渲染

思路:在這之前,我們已經把data中的屬性改成響應式了,當去獲取或者修改這些變數時便能夠觸發相應函式。那這裡就可以利用這個相應的函式做些手腳了。當宣告一個vue物件時,在執行render函式獲取虛擬dom的這個過程中,已經對render中依賴的data屬性進行了一次獲取操作,這次獲取操作便可以拿到所有依賴。

其實不僅是render,任何一個變數的改別,是因為別的變數改變引起,都可以用上述方法,也就是computedwatch的原理

首先需要寫一個依賴收集的類,每一個data中的屬性都有可能被依賴,因此每個屬性在響應式轉化(defineReactive)的時候,就初始化它。程式碼如下:

class Dep {
  constructor() {
    this.subs = []
  }
  add(cb) {
    this.subs.push(cb)
  }
  notify() {
    console.log(this.subs)
    this.subs.forEach((cb) => cb())
  }
}

function defineReactive(obj, key, val, cb) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    // 省略
  })
}

那麼執行過程就是:
- 當執行render函式的時候,依賴到的變數的get就會被執行,然後就把這個 render函式加到subs裡面去。
- 當set的時候,就執行notify,將所有的subs數組裡的函式執行,其中就包含render的執行。

注:程式碼中有一個Dep.target值,這個值時用來區分是普通的get還是收集依賴時的get

最後完整程式碼如下:

function VNode(tag, data, children, text) {
  return {
    tag: tag,
    data: data,
    children: children,
    text: text
  }
}

class Vue {
  constructor(options) {
    this.$options = options
    this._data = options.data
    Object.keys(options.data).forEach(key => this._proxy(key))
    observer(options.data)
    const vdom = watch(this, this._render.bind(this), this._update.bind(this))
    console.log(vdom)
  }
  _proxy(key) {
    const 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
      }
    })
  }
  _update() {
    console.log("我需要更新");
    const vdom = this._render.call(this)
    console.log(vdom);
  }
  _render() {
    return this.$options.render.call(this)
  }
  __h__(tag, attr, children) {
    return VNode(tag, attr, children.map((child) => {
      if (typeof child === 'string') {
        return VNode(undefined, undefined, undefined, child)
      } else {
        return child
      }
    }))
  }
  __toString__(val) {
    return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val);
  }
}

function observer(obj) {
  Object.keys(obj).forEach((key) => {
    if (typeof obj[key] === 'object') {
      new observer(obj[key])
    }
    defineReactive(obj, key, obj[key])
  })
}

function defineReactive(obj, key, val) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: () => {
      if (Dep.target) {
        dep.add(Dep.target)
        Dep.target = null
      }
      console.log('你訪問了' + key)
      return val
    },
    set: newVal => {
      if (newVal === val)
        return
      console.log('你設定了' + key)
      console.log('新的' + key + ' = ' + newVal)
      val = newVal
      dep.notify()
    }
  })
}

function watch(vm, exp, cb) {
  Dep.target = cb
  return exp()
}

class Dep {
  constructor() {
    this.subs = []
  }
  add(cb) {
    this.subs.push(cb)
  }
  notify() {
    this.subs.forEach((cb) => cb())
  }
}
Dep.target = null


var demo = new Vue({
  el: '#demo',
  data: {
    text: "before",
    test: {
      a: '1'
    },
    t: 1
  },
  render() {
    return this.__h__('div', {}, [
      this.__h__('span', {}, [this.__toString__(this.text)]),
      this.__h__('span', {}, [this.__toString__(this.test.a)])
    ])
  }
})

vue react響應式簡單對比

綜上發現,利用Object.defineProperty這個特性可以精確的寫出訂閱釋出模式,從這點來說,vue是優於react的,在沒經過優化之前,vue的渲染機制一定是比react更加準確的,為了驗證這一說法,我用兩個框架同時寫了兩個相同的簡單專案進行對比。

沒有對比就沒有傷害:

通過對比發現,react在正常使用的過程中產生了多餘的渲染,在移動端或者元件巢狀非常深的情況下會產生非常大的效能消耗,因此在使用react的過程中,寫好react生命週期中的shouldComponentUpdate是非常重要的!

 參考

相關推薦

vue2.x響應原理vuereact響應簡單對比

配合ppt食用更佳ppt 實現的最終目標 const demo = new Vue({ data: { text: "before", }, // 對應的template 為 <div><span>{{text

當面試官問你Vue響應原理你可以這麼回答他

const Observer = function(data) { for (let key in data) { defineReactive(data, key); } } const defineReactive = function(obj, key) { const dep =

Vue2.x源碼學習筆記-Vue靜態方法和靜態屬性整理

temp next 技術 spa delet 結構 又是 靜態 https Vue靜態方法和靜態屬性,其實直接在瀏覽器中可以查看到的,如下 圈起來的是其靜態屬性,但是有的屬性對象中的屬性的值又是函數。未圈起來的則是函數。 其實它來自如下各個目錄下的js文件 // src

AD轉換原理器件引數

1 內容簡介 對AD晶片基本原理,分類與關鍵技術引數指標進行整理介紹。 2 模數轉換 2.1 轉換原理類別 AD轉換就是模數轉換。顧名思義,就是把模擬訊號轉換成數字訊號。主要包括積分型、逐次逼近型、並行比較型/串並行型、Σ-Δ調製型、壓頻變換型。A/D轉換器是用來通過一定的電路將模擬量轉變

http server原理nginxphp之間是如何工作的

Nginx (“engine x”) 是一個高效能的 HTTP 和 反向代理 伺服器,也是一個 IMAP/POP3/SMTP 代理伺服器。 Nginx 是由 Igor Sysoev 為俄羅斯訪問量第二的 Rambler.ru 站點開發的,第一個公開版本0.1.0釋出於2004年10月4日。其將原始碼

Redis: 2、Redis高可用原理搭建驗證

Redis高可用原理,搭建與驗證   一、redis-ha原理 1 原理 redis高可用採用的是哨兵(sentinel),多個redis-slave配備了多個哨兵程序,哨兵監控redis-master,一旦出現故障,將一臺slave提升為master。客戶端通過連線哨

vuereact對比相同之處不同之處。

兩者都為當下主流框架 相同之處在於: 一、使用 Virtual DOM 二、提供了響應式 (Reactive) 和元件化 (Composable) 的檢視元件。 三、將注意力集中保持在核心庫,而將其他功能如路由和全域性狀態管理交給相關的庫。 不同之處: 一、在 Rea

uva 12093 Protecting Zonk(在某個節點X使用A裝置此時節點X相連的邊都被覆蓋)

題意: 有一個n(n<=10000)個節點的無根樹。有兩種裝置A,B,每種都有無限多個。 1.在某個節點X使用A裝置需要C1(C1<=1000)的花費,並且此時與節點X相連

vue2.X基礎知識八之vue-router路由

  前端路由是直接找到與地址匹配的一個元件或物件並將其渲染出來。改變瀏覽器地址而不向伺服器發出請求有兩種做法,一是在地址中加入#以欺騙瀏覽器,地址的改變是由於正在進行頁內導航;二是使用HTML5的window.history功能,使用URL的Hash來模擬一個完整的URL。將

線段樹詳解 (原理實現應用)

比如,以左側的的藍色為例,若該節點是其父節點的右子節點,就證明它右側的那個紫色節點不會留下,會被其父替代,所以沒必要在這一步計算,若該節點是其父節點的左子節點,就證明它右側的那個紫色節點會留在這一層,所以必須在此刻計算,否則以後都不會再計算這個節點了。這樣逐層上去,容易發現,對於左側的藍色節點來說,只要它是左

淘寶、網易移動端 px 轉換 rem 原理Vue-cli 實現 px 轉換rem

   在過去的一段時間裡面一直在使用Vue配合 lib-flexible和px2rem-loader配合做移動端的網頁適配。秉著求知的思想,今天決定對他的原理進行分析。目前網上比較主流使用的就是淘寶方案和網易的解決方案,所以今天我就從這兩方面入手深度瞭解這兩個方案。本著網際網路分享的精神我會將我所理解的內容分

octavia的實現分析(二)·原理架構基本流程

【瞭解】 其實說白了,Octavia就是將使用者的API請求經過邏輯處理,轉換成Haproxy或者Nginx的配置引數,下發到amphora虛機中。 Octavia的內部實現中,邏輯流程的處理主要使用TaskFlow庫。     【基本概念】 ·LBaas

C# 關鍵字explicit(顯示)implicit(隱類型的隱和顯轉換

tar oid bsp color col 必須 code 類型 顯示 class Program { static void Main(string[] args) { Adaptee ada = ne

Vue2實踐揭秘 - 書讀後作了一個簡單摘要

邏輯 sel blog slides 執行者 emp 文件名 pla 最好 jd上買了本實踐相關的, 看過後,的確是實踐項目後的一些分享,有些網上的一些vue2教程沒怎麽提及 ----------- 看完了,有些啟發,作了個簡單摘要作記錄, 對vue2感興趣的,可以自己網

vuereact的區別

reac setter 表達式 react 輕量級框架 轉換 簡單 用戶 ets 相同點:都是基於組件化的輕量級框架,都專註於構建用戶界面的視圖層 vue,react都會構建一個虛擬的DOM並同步帶真是的DOM中 vue數據綁定表達式使用過的雙大括號語法,而指令是用於向

Day4 閉包、裝飾器decorator、叠代器生成器、面向過程編程、三元表達、列表解析生成器表達、序列化反序列化

反序 bsp pic nbsp tor 序列 space 列表解析 列表 http://pic.cnhubei.com/space.php?uid=1774&do=album&id=1362489http://pic.cnhubei.com/space.ph

[轉]什麽是分布系統如何學習分布系統

簡單 配置文件 延遲 去中心化 入門 應該 body base 技術分享 什麽是分布式系統 分布式系統挑戰 分布式系統特性與衡量標準 組件、理論、協議 用一個請求串起來 一個簡化的架構圖 概念與實現 總結 references

什麽是分布系統如何學習分布系統

zook 並發 運營 app oop 錯誤 都是 衡量標準 god 目錄 什麽是分布式系統 分布式系統挑戰 分布式系統特性與衡量標準 組件、理論、協議 用一個請求串起來 一個簡化的架構圖 概念與實現 總結 references 正文   雖然本人在前面也

VueReact的異同 -生命週期

vue的生命週期 建立前 beforeCreate 建立   create 掛載前 beforeMount 掛載 mounted 更新前 beforeUpdate 更新 updated 銷燬前 beforeDestroyed 銷燬  destoryed  met