我以前介紹過vue1.0如何實現observer
和watcher
。本想繼續寫下去,但是vue2.0橫空出世..因此
直接看vue2.0吧。這篇文章在公司分享過,終於寫出來了。咱們採用用最精簡的代碼,還原vue2.0響應式架構實現
之前寫的那篇 vue 源碼分析之如何實現 observer 和 watcher能夠做爲本次分享的參考。
不過不看也不要緊,可是最好了解下Object.definePropertyjavascript
理解vue2.0的響應式架構,就是下面這張圖
html
順帶介紹他比react快的其中一個緣由vue
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>
好,開始吧!!!java
來來來先看代碼吧node
class Vue { constructor(options) { this.$options = options this._data = options.data observer(options.data, this._update) this._update() } _update(){ this.$options.render() } } function observer(value, cb){ Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb)) } function defineReactive(obj, key, val, cb) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>{}, set:newVal=> { cb() } }) } var demo = new Vue({ el: '#demo', data: { text: 123, }, render(){ console.log("我要render了") } }) setTimeout(function(){ demo._data.text = 444 }, 3000)
爲了好演示咱們只考慮最簡單的狀況,若是看了vue 源碼分析之如何實現 observer 和 watcher可能就會很好理解,不過不要緊,咱們三言兩語再說說,這段代碼要實現的功能就是將react
var demo = new Vue({ el: '#demo', data: { text: 123, }, render(){ console.log("我要render了") } })
中data
裏面全部的屬性置於 observer,而後data
裏面的屬性,好比 text
以改變,就引發_update()
函數調用進而從新渲染,是怎樣作到的呢,咱們知道其實就是賦值的時候就要改變對吧,當我給data
下面的text
賦值的時候 set
函數就會觸發,這個時候 調用 _update
就ok了,可是git
setTimeout(function(){ demo._data.text = 444 }, 3000)
demo._data.text
沒有demo.text
用着爽,不要緊,咱們加一個代理github
_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 } }) }
而後在Vue
的constructor
加上下面這句segmentfault
Object.keys(options.data).forEach(key => this._proxy(key))
第一步先說到這裏,咱們會發現一個問題,data
中任何一個屬性的值改變,都會引發_update
的觸發進而從新渲染,屬性這顯然不夠精準啊數組
好比考慮下面代碼
new Vue({ template: ` <div> <section> <span>name:</span> {{name}} </section> <section> <span>age:</span> {{age}} </section> <div>`, data: { name: 'js', age: 24, height: 180 } }) setTimeout(function(){ demo.height = 181 }, 3000)
template
裏面只用到了data
上的兩個屬性name
和age
,可是當我改變height
的時候,用第一步的代碼,會不會觸發從新渲染?會!,但其實不須要觸發從新渲染,這就是問題所在!!
首先,template最後都是編譯成render函數的(具體怎麼作,就不展開說了,之後我會說的),而後render 函數執行完就會獲得一個虛擬DOM,爲了好理解咱們寫寫最簡單的虛擬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)]) ]) } })
咱們運行一下,他會輸出
{ tag: 'div', data: {}, children:[ { tag: 'span', data: {}, children: [ { children: undefined, data: undefined, tag: undefined, text: '' // 正常狀況爲 字符串 before,由於咱們爲了演示就不寫代理的代碼,因此這裏爲空 } ] } ] }
這就是 虛擬最簡單虛擬DOM
,tag
是html
標籤名,data
是包含諸如 class
和 style
這些標籤上的屬性,childen
就是子節點,關於虛擬DOM就不展開說了。
回到開始的問題,也就是說,我得知道,render
函數裏面依賴了vue實例裏面哪些變量(只考慮render 就能夠,由於template 也會是幫你編譯成render)。敘述有點拗口,仍是看代碼吧
var demo = new Vue({ el: '#demo', data: { text: "before", name: "123", age: 23 }, render(){ return this.__h__('div', {}, [ this.__h__('span', {}, [this.__toString__(this.text)]) ]) } })
就像這段代碼,render
函數裏其實只依賴text
,並無依賴 name
和 age
,因此,咱們只要text
改變的時候
咱們自動觸發 render
函數 讓它生成一個虛擬DOM
就ok了(剩下的就是這個虛擬DOM和上個虛擬DOM
作比對,而後操做真實DOM
,只能之後再說了),那麼咱們正式考慮一下怎麼作
回到最上面那張圖,咱們知道data
上的屬性設置defineReactive
後,修改data 上的值會觸發 set
。
那麼咱們取data
上值是會觸發 get
了。
對,咱們能夠在上面作作手腳,咱們先執行一下render
,咱們看看data
上哪些屬性觸發了get
,咱們豈不是就能夠知道 render
會依賴data
上哪些變量了。
而後我麼把這些變量作些手腳,每次這些變量變的時候,咱們就觸發render
。
上面這些步驟簡單用四個子歸納就是 計算依賴。
(其實不只是render
,任何一個變量的改別,是由於別的變量改變引發,均可以用上述方法,也就是computed
和 watch
的原理,也是mobx的核心)
咱們寫一個依賴收集的類,每個data
上的對象都有可能被render
函數依賴,因此每一個屬性在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
函數去'touch'依賴的時候,依賴到的變量get就會被執行,而後咱們就能夠把這個 render
函數加到 subs
裏面去了。
當咱們,set
的時候 咱們就執行 notify
將全部的subs數組裏的函數執行,其中就包含render
的執行。
至此就完成了整個圖,好咱們將全部的代碼展現出來
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.text = 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(value, cb){ Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb)) } function defineReactive(obj, key, val, cb) { const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>{ if(Dep.target){ dep.add(Dep.target) } return val }, set: newVal => { if(newVal === val) return 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", }, render(){ return this.__h__('div', {}, [ this.__h__('span', {}, [this.__toString__(this.text)]) ]) } }) setTimeout(function(){ demo.text = "after" }, 3000)
咱們看一下運行結果
好咱們解釋一下 Dep.target
由於咱們得區分是,普通的get
,仍是在查找依賴的時候的get
,
全部咱們在查找依賴時候,咱們將
function watch(vm, exp, cb){ Dep.target = cb return exp() }
Dep.target
賦值,至關於 flag 一下,而後 get
的時候
get: () => { if (Dep.target) { dep.add(Dep.target) } return val },
判斷一下,就行了。
到如今爲止,咱們再看那張圖是否是就清楚不少了?
我很是喜歡,vue2.0 以上代碼爲了好展現,都採用最簡單的方式呈現。
不過整個代碼執行過程,甚至是命名方式都和vue2.0同樣
對比react,vue2.0 自動幫你監測依賴,自動幫你從新渲染,而
react 要實現性能最大化,要作大量工做,好比我之前分享的
react如何性能達到最大化(前傳),暨react爲啥非得使用immutable.js
react 實現pure render的時候,bind(this)隱患。
而 vue2.0 自然幫你作到了最優,並且對於像萬年不變的 如標籤上靜態的class
屬性,
vue2.0 在從新渲染後作diff 的時候是不比較的,vue2.0比 達到性能最大化的react 還要快的一個緣由
而後源碼在此,喜歡的記得給個 star 哦? 後續,我會簡單聊聊,vue2.0的diff。 若是有疑問,能夠在評論區留言哈