国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > JS > 正文

100行代碼理解和分析vue2.0響應式架構

2024-05-06 16:35:39
字體:
來源:轉載
供稿:網友

分享前啰嗦

我之前介紹過vue1.0如何實現observerwatcher。本想繼續寫下去,可是vue2.0橫空出世..所以直接看vue2.0吧。這篇文章在公司分享過,終于寫出來了。我們采用用最精簡的代碼,還原vue2.0響應式架構實現。

以前寫的那篇 vue 源碼分析之如何實現 observer 和 watcher可以作為本次分享的參考。

不過不看也沒關系,但是最好了解下Object.defineProperty

本文分享什么

理解vue2.0的響應式架構,就是下面這張圖

vue2.0,響應式架構

順帶介紹他比react快的其中一個原因

本分實現什么

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 下面所有屬性變為observable

來來來先看代碼吧

 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可能就會很好理解,不過沒關系,我們三言兩語再說說,這段代碼要實現的功能就是將

 var demo = new Vue({ el: '#demo', data: { text: 123, }, render(){ console.log("我要render了") } })

中data 里面所有的屬性置于 observer,然后data里面的屬性,比如 text 以改變,就引起_update()函數調用進而重新渲染,是怎樣做到的呢,我們知道其實就是賦值的時候就要改變對吧,當我給data下面的text 賦值的時候 set 函數就會觸發,這個時候 調用 _update 就ok了,但是

 setTimeout(function(){ demo._data.text = 444 }, 3000)

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  } }) }

然后在Vue的constructor加上下面這句

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上的兩個屬性nameage,但是當我改變height的時候,用第一步的代碼,會不會觸發重新渲染?會!,但其實不需要觸發重新渲染,這就是問題所在!!

第三步,上述問題怎么解決
簡單說說虛擬 DOM
首先,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,taghtml 標簽名,data 是包含諸如classstyle這些標簽上的屬性,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,只能以后再說了),那么我們正式考慮一下怎么做

第三步,'touch' 拿到依賴

回到最上面那張圖,我們知道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)

我們看一下運行結果

vue2.0,響應式架構

好我們解釋一下 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。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 德州市| 绥棱县| 清水县| 乐亭县| 南郑县| 华亭县| 凭祥市| 南涧| 建湖县| 宁远县| 江达县| 桃园县| 寿光市| 汨罗市| 石台县| 肥乡县| 连山| 岚皋县| 镇远县| 那坡县| 沈阳市| 正宁县| 自治县| 盐亭县| 汝州市| 扎赉特旗| 青铜峡市| 攀枝花市| 灵丘县| 渝中区| 锡林郭勒盟| 墨江| 鹿邑县| 阜康市| 河曲县| 安徽省| 石台县| 平泉县| 镇宁| 黄骅市| 华阴市|