Vue的data、computed、watch源碼淺談_vue.js

來源:腳本之家  責任編輯:小易  

樓上是機器翻譯的垃圾!我是法語專業的,我幫你手譯了:J'ai décidé d'étudier en France pour mon avenir.Ma mère est le président du conseil d'administration d'une société de laconstruction routière.Elle fait toujours la communication commercialeavec des sociétés fran?aises en raison d'utiliser leur nouveaux matériauxet nouveaux équipements.Cela me fait savoir que grace à la communicationplus ouverte,de plus en plus de sociétés excellentes fran?aises entronten Chine en vue de faire l'expasion,Il y a donc plus de besoinsdu personnel professional qui a la capacité de la communication en fran?aiset connait bien le commerce international et la situation sino-fran?aise.Par conséquent,j'ai choisi de me perfectionner en France dans la gestionéconomique de la société.Je me efforcerai de devenir ce qui est précité pourjeter les bases solides de mon progrès ultérieurwww.13333515.buzz防采集請勿采集本網。

導讀

記得初學Vue源碼的時候,在defineReactive、Observer、Dep、Watcher等等內部設計源碼之間跳來跳去,發現再也繞不出來了。Vue發展了很久,很多fix和feature的增加讓內部源碼越來越龐大,太多的邊界情況和優化設計掩蓋了原本精簡的代碼設計,讓新手閱讀源碼變得越來越困難,但是面試的時候,Vue的響應式原理幾乎成了Vue技術棧的公司面試中高級前端必問的點之一。

每個人呢只想那些自己感興趣的事情,這樣不對。L'un des avantages de cette compétition réside dans ce que nous avons fait connaissance de plusieurs savants et amis.

這篇文章通過自己實現一個響應式系統,盡量還原和Vue內部源碼同樣結構,但是剔除掉和渲染、優化等等相關的代碼,來最低成本的學習Vue的響應式原理。

plus de 800 travailleurs,y compris le commerce,la technologie,gestion de production,le personnel d'environ 100 personnes.Des équipements sophistiqués entreprises,la force de solides compé

預覽

源碼地址(ts):

formes beaucoup placent des troves de trésor de vue,devient Shanxi à prendre n'épuise pas,emploie les ressources de touristes sans fin. 這種東西也要找人以這種方式翻譯!

https://github.com/sl1673495/vue-reactive

Géographes(地理學家):géographe est petit prince avant d'atteindre la cinquième personne vu la Terre,aussi le dernier.Géographes ressemble très compétent,il sait où l'océan,les rivi

源碼地址(js)

De ce point de vue,les entreprise fixent leurs attentions sur les expériences de stage ce qui deviennent un des atouts absolus. Apres tout,notre priorité consiste à nos etudes a l'ecole,

https://github.com/sl1673495/vue-reactive/tree/js-version

預覽地址:

https://sl1673495.github.io/vue-reactive/

reactive

Vue最常用的就是響應式的data了,通過在vue中定義

new Vue({ data() { return { msg: 'Hello World' } }})

在data發生改變的時候,視圖也會更新,在這篇文章里我把對data部分的處理單獨提取成一個api:reactive,下面來一起實現這個api。

要實現的效果:

const data = reactive({ msg: 'Hello World',})new Watcher(() => { document.getElementById('app').innerHTML = `msg is ${data.msg}`})

在data.msg發生改變的時候,我們需要這個app節點的innerHTML同步更新,這里新增加了一個概念Watcher,這也是Vue源碼內部的一個設計,想要實現響應式的系統,這個Watcher是必不可缺的。

在實現這兩個api之前,我們先來理清他們之間的關系,reactive這個api定義了一個響應式的數據,其實大家都知道響應式的數據就是在它的某個屬性(比如例中的data.msg)被讀取的時候,記錄下來這時候是誰在讀取他,讀取他的這個函數肯定依賴它。

在本例中,下面這段函數,因為讀取了data.msg并且展示在頁面上,所以可以說這段渲染函數依賴了data.msg。

// 渲染函數document.getElementById('app').innerHTML = `msg is ${data.msg}`

這也就解釋清了,為什么我們需要用new Watcher來傳入這段渲染函數,我們已經可以分析出來Watcher是幫我們記錄下來這段渲染函數依賴的關鍵。

在js引擎執行渲染函數的途中,突然讀到了data.msg,data已經被定義成了響應式數據,讀取data.msg時所觸發的get函數已經被我們劫持,這個get函數中我們去記錄下data.msg被這個渲染函數所依賴,然后再返回data.msg的值。

這樣下次data.msg發生變化的時候,Watcher內部所做的一些邏輯就會通知到渲染函數去重新執行。這不就是響應式的原理嘛。

下面開始實現代碼

import Dep from './dep'import { isObject } from '../utils'// 將對象定義為響應式export default function reactive(data) { if (isObject(data)) { Object.keys(data).forEach(key => { defineReactive(data, key) }) } return data}function defineReactive(data, key) { let val = data[key] // 收集依賴 const dep = new Dep() Object.defineProperty(data, key, { get() { dep.depend() return val }, set(newVal) { val = newVal dep.notify() } }) if (isObject(val)) { reactive(val) }}

代碼很簡單,就是去遍歷data的key,在defineReactive函數中對每個key進行get和set的劫持,Dep是一個新的概念,它主要用來做上面所說的dep.depend()去收集當前正在運行的渲染函數和dep.notify() 觸發渲染函數重新執行。

可以把dep看成一個收集依賴的小筐,每當運行渲染函數讀取到data的某個key的時候,就把這個渲染函數丟到這個key自己的小筐中,在這個key的值發生改變的時候,去key的筐中找到所有的渲染函數再執行一遍。

Dep

export default class Dep { constructor() { this.deps = new Set() } depend() { if (Dep.target) { this.deps.add(Dep.target) } } notify() { this.deps.forEach(watcher => watcher.update()) }}// 正在運行的watcherDep.target = null

這個類很簡單,利用Set去做存儲,在depend的時候把Dep.target加入到deps集合里,在notify的時候遍歷deps,觸發每個watcher的update。

沒錯Dep.target這個概念也是Vue中所引入的,它是一個掛在Dep類上的全局變量,js是單線程運行的,所以在渲染函數如:

document.getElementById('app').innerHTML = `msg is ${data.msg}`

運行之前,先把全局的Dep.target設置為存儲了這個渲染函數的watcher,也就是:

new Watcher(() => { document.getElementById('app').innerHTML = `msg is ${data.msg}`})

這樣在運行途中data.msg就可以通過Dep.target找到當前是哪個渲染函數的watcher正在運行,這樣也就可以把自身對應的依賴所收集起來了。

這里劃重點:Dep.target一定是一個Watcher的實例。

又因為渲染函數可以是嵌套運行的,比如在Vue中每個組件都會有自己用來存放渲染函數的一個watcher,那么在下面這種組件嵌套組件的情況下:

// Parent組件<template> <div> <Son組件 /> </div></template>

watcher的運行路徑就是: 開始 -> ParentWatcher -> SonWatcher -> ParentWatcher -> 結束。

是不是特別像函數運行中的入棧出棧,沒錯,Vue內部就是用了棧的數據結構來記錄watcher的運行軌跡。

// watcher棧const targetStack = []// 將上一個watcher推到棧里,更新Dep.target為傳入的_target變量。export function pushTarget(_target) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target}// 取回上一個watcher作為Dep.target,并且棧里要彈出上一個watcher。export function popTarget() { Dep.target = targetStack.pop()}

有了這些輔助的工具,就可以來看看Watcher的具體實現了

import Dep, { pushTarget, popTarget } from './dep'export default class Watcher { constructor(getter) { this.getter = getter this.get() } get() { pushTarget(this) this.value = this.getter() popTarget() return this.value } update() { this.get() }}

回顧一下開頭示例中Watcher的使用。

const data = reactive({ msg: 'Hello World',})new Watcher(() => { document.getElementById('app').innerHTML = `msg is ${data.msg}`})

傳入的getter函數就是

() => { document.getElementById('app').innerHTML = `msg is ${data.msg}`}

在構造函數中,記錄下getter函數,并且執行了一遍get

get() { pushTarget(this) this.value = this.getter() popTarget() return this.value }

在這個函數中,this就是這個watcher實例,在執行get的開頭先把這個存儲了渲染函數的watcher設置為當前的Dep.target,然后執行this.getter()也就是渲染函數

在執行渲染函數的途中讀取到了data.msg,就觸發了defineReactive函數中劫持的get:

Object.defineProperty(data, key, { get() { dep.depend() return val } })

這時候的dep.depend函數:

depend() { if (Dep.target) { this.deps.add(Dep.target) } }

所收集到的Dep.target,就是在get函數開頭中pushTarget(this)所收集的

new Watcher(() => { document.getElementById('app').innerHTML = `msg is ${data.msg}`})

這個watcher實例了。

此時我們假如執行了這樣一段賦值代碼:

data.msg = 'ssh'

就會運行到劫持的set函數里:

Object.defineProperty(data, key, { set(newVal) { val = newVal dep.notify() } })

此時在控制臺中打印出dep這個變量,它內部的deps屬性果然存儲了一個Watcher的實例。

運行了dep.notify以后,就會觸發這個watcher的update方法,也就會再去重新執行一遍渲染函數了,這個時候視圖就刷新了。

computed

在實現了reactive這個基礎api以后,就要開始實現computed這個api了,這個api的用法是這樣:

const data = reactive({ number: 1})const numberPlusOne = computed(() => data.number + 1)// 渲染函數watchernew Watcher(() => { document.getElementById('app2').innerHTML = ` computed: 1 + number 是 ${numberPlusOne.value} `})

vue內部是把computed屬性定義在vm實例上的,這里我們沒有實例,所以就用一個對象來存儲computed的返回值,用.value來拿computed的真實值。

這里computed傳入的其實還是一個函數,這里我們回想一下Watcher的本質,其實就是存儲了一個需要在特定時機觸發的函數,在Vue內部,每個computed屬性也有自己的一個對應的watcher實例,下文中叫它computedWatcher

先看渲染函數:

// 渲染函數watchernew Watcher(() => { document.getElementById('app2').innerHTML = ` computed: 1 + number 是 ${numberPlusOne.value} `})

這段渲染函數執行過程中,讀取到numberPlusOne的值的時候

首先會把Dep.target設置為numberPlusOne所對應的computedWatcher

computedWatcher的特殊之處在于

    渲染watcher只能作為依賴被收集到其他的dep筐子里,而computedWatcher實例上有屬于自己的dep,它可以收集別的watcher作為自己的依賴。

    惰性求值,初始化的時候先不去運行getter。

export default class Watcher { constructor(getter, options = {}) { const { computed } = options this.getter = getter this.computed = computed if (computed) { this.dep = new Dep() } else { this.get() } }}

其實computed實現的本質就是,computed在讀取value之前,Dep.target肯定此時是正在運行的渲染函數的watcher。

先把當前正在運行的渲染函數的watcher作為依賴收集到computedWatcher內部的dep筐子里。

把自身computedWatcher設置為 全局Dep.target,然后開始求值:

求值函數會在運行() => data.number + 1的途中遇到data.number的讀取,這時又會觸發'number'這個key的劫持get函數,這時全局的Dep.target是computedWatcher,data.number的dep依賴筐子里丟進去了computedWatcher。

此時的依賴關系是 data.number的dep筐子里裝著computedWatcher,computedWatcher的dep筐子里裝著渲染watcher。

此時如果更新data.number的話,會一級一級往上觸發更新。會觸發computedWatcher的update,我們肯定會對被設置為computed特性的watcher做特殊的處理,這個watcher的筐子里裝著渲染watcher,所以只需要觸發 this.dep.notify(),就會觸發渲染watcher的update方法,從而更新視圖。

下面來改造代碼:

// Watcherimport Dep, { pushTarget, popTarget } from './dep'export default class Watcher { constructor(getter, options = {}) { const { computed } = options this.getter = getter this.computed = computed if (computed) { this.dep = new Dep() } else { this.get() } } get() { pushTarget(this) this.value = this.getter() popTarget() return this.value } // 僅為computed使用 depend() { this.dep.depend() } update() { if (this.computed) { this.get() this.dep.notify() } else { this.get() } }}

computed初始化:

// computedimport Watcher from './watcher'export default function computed(getter) { let def = {} const computedWatcher = new Watcher(getter, { computed: true }) Object.defineProperty(def, 'value', { get() { // 先讓computedWatcher收集渲染watcher作為自己的依賴。 computedWatcher.depend() return computedWatcher.get() } }) return def}

這里的邏輯比較繞,如果沒理清楚的話可以把代碼下載下來一步步斷點調試,data.number被劫持的set觸發以后,可以看一下number的dep到底存了什么。

watch

watch的使用方式是這樣的:

watch( () => data.msg, (newVal, oldVal) => { console.log('newVal: ', newVal) console.log('old: ', oldVal) })

傳入的第一個參數是個函數,里面需要讀取到響應式的屬性,確保依賴能被收集到,這樣下次這個響應式的屬性發生改變后,就會打印出對飲的新值和舊值。

分析一下watch的實現原理,這里依然是利用Watcher類去實現,我們把用于watch的watcher叫做watchWatcher,傳入的getter函數也就是() => data.msg,Watcher在執行它之前還是一樣會把自身(也就是watchWatcher)設為Dep.target,這時讀到data.msg,就會把watchWatcher丟進data.msg的依賴筐子里。

如果data.msg更新了,則就會觸發watchWatcher的update方法

直接上代碼:

// watchimport Watcher from './watcher'export default function watch(getter, callback) { new Watcher(getter, { watch: true, callback })}

沒錯又是直接用了getter,只是這次傳入的選項是{ watch: true, callback },接下來看看Watcher內部進行了什么處理:

export default class Watcher { constructor(getter, options = {}) { const { computed, watch, callback } = options this.getter = getter this.computed = computed this.watch = watch this.callback = callback this.value = undefined if (computed) { this.dep = new Dep() } else { this.get() } }}

首先是構造函數中,對watch選項和callback進行了保存,其他沒變。

然后在update方法中。

update() { if (this.computed) { ... } else if (this.watch) { const oldValue = this.value this.get() this.callback(oldValue, this.value) } else { ... } }

在調用this.get去更新值之前,先把舊值保存起來,然后把新值和舊值一起通過調用callback函數交給外部,就這么簡單。

我們僅僅是改動寥寥幾行代碼,就輕松實現了非常重要的api:watch。

總結。

有了精妙的Watcher和Dep的設計,Vue內部的響應式api實現的非常簡單,不得不再次感嘆一下尤大真是厲害啊!

到此這篇關于Vue的data、computed、watch源碼淺談的文章就介紹到這了,更多相關Vue data、computed、watch源碼內容請搜索真格學網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持真格學網!

哦,嘿,你好,你好,你好嗎?是的,是的,你在這里為虛擬發型嗎?是的,是的,好吧,我將去路易吉。他要來剪你的頭發。我-我曼紐爾,只是…只是留在那兒。嗯.路易吉?路易吉?啊路易!這是曼努埃爾在這里。嗯人在這里為虛擬發型。你最好上來。修道院曼紐爾,我現在就來。呃好吧,他,他來了…現在,與此同時我將會在這里和播放音樂,彈吉他,因為這是我在這里在理發店。嗯。啊,很高興見到你。歡迎來到斯達克理發店和你的虛擬發型等等。我想開始演示通過移動到你的右手邊,拾起這個袋子。如果你只是靜止不動一秒鐘,我將把這個袋子在你的頭上就這樣。袋子的頭頂,現在我把袋子掉。我們走吧。我做的唯一原因,是因為所有這些高檔理發店這樣做。你聽我離開你就在這里,很快洗我的手。曼紐爾,你能得到,好嗎?嗯.是的…嗯。啊謝謝你曼紐爾。讓我洗完我的手在這里。只是忍受我,幾秒。啊,我們走吧。啊,是的,我是說…我們正在做的事就是用你的頭作為監聽點。和我們有兩個麥克風,一頭的兩邊,在相同的位置是在你的左和右耳朵。你的大腦在做所有的工作,告訴你的聲音是來自。好吧,我去拿剪刀。好和鋒利的。現在,當我開始剪裁,我把快船隊接近你的耳朵,非常接近正確的耳朵,跟著我當我四處移動的后腦勺,左耳朵,并在頂部的頭。好吧,現在,你可以得到同樣的效果更好的電動剃須刀。我先把它接近你的右耳朵,它是完美的?和在背部,到你的左,右,我認為看起來很棒。曼紐爾,你怎么看?嗯?什么?嗯。哇。是的,是的,它看起來真棒路易吉。你做這樣的好工作。啊謝謝你這么多曼努埃爾。這么快過。現在,當我四處走走,我只是想告訴你,你的能力再一次聽到我在哪里,當我走在房間里僅僅是你大腦的驚人力量,計算微小差異,或暗示在聲音強度和到達時間從兩個開放的耳朵。與其他聽力儀器,只有一個有數字算法,否定自己的身體出現在ear完全恢復這些差異。這種算法被稱為…等等。好的,很感謝你,感謝你停止由斯達克虛擬理發店,再見內容來自www.13333515.buzz請勿采集。


  • 本文相關:
  • vue中watch和computed為什么能監聽到數據的改變以及不同之處
  • vue中computed、methods與watch的區別總結
  • vue計算屬性computed、事件、監聽器watch的使用講解
  • vue中的methods、watch、computed的區別
  • vue的watch和computed方法的使用及區別介紹
  • 深入淺析vue中的 computed 和 watch
  • vue中計算屬性(computed)、methods和watched之間的區別
  • 詳解vue中computed 和 watch的異同
  • vue.js計算屬性computed與watch(5)
  • vue.js每天必學之計算屬性computed與$watch
  • spring boot/vue中路由傳遞參數的實現代碼
  • element-ui中table表格省市區合并單元格的方法實現
  • vue2 vue-cli中使用typescript的配置詳解
  • 最后說說vue2 ssr 的 cookies 問題
  • vue實現星級評價效果實例詳解
  • vue腳手架編寫試卷頁面功能
  • vue.js實現表格合并示例代碼
  • 分分鐘玩轉vue.js組件(二)
  • 基于vue實現web端超大數據量表格的卡頓解決
  • vue.js 點擊按鈕顯示/隱藏內容的實例代碼
  • 英文段子翻譯成中文
  • 請高手幫忙把這段文字翻譯成法語
  • 求a notre sante的歌詞大意,是Benabar唱的。
  • 請幫我把這段文章譯成法語,謝謝
  • 漢法翻譯
  • 中文翻譯成法文
  • 請高手把以下內容翻譯成法語,多謝老師,(本人是新手,積分已用完)
  • 小王子法語人物介紹
  • 麻煩高手幫忙把下面短文翻譯成法語(50分)
  • décliner是什么意思 《法語助手》法漢
  • 網站首頁網頁制作腳本下載服務器操作系統網站運營平面設計媒體動畫電腦基礎硬件教程網絡安全yui.ext相關prototypejqueryangularjsjsonlib_jsjs面向對象extjsmootoolsseajsdojovue.jsbackbone.js其它首頁javascriptjavascript類庫vue.jsvue中watch和computed為什么能監聽到數據的改變以及不同之處vue中computed、methods與watch的區別總結vue計算屬性computed、事件、監聽器watch的使用講解vue中的methods、watch、computed的區別vue的watch和computed方法的使用及區別介紹深入淺析vue中的 computed 和 watchvue中計算屬性(computed)、methods和watched之間的區別詳解vue中computed 和 watch的異同vue.js計算屬性computed與watch(5)vue.js每天必學之計算屬性computed與$watchspring boot/vue中路由傳遞參數的實現代碼element-ui中table表格省市區合并單元格的方法實現vue2 vue-cli中使用typescript的配置詳解最后說說vue2 ssr 的 cookies 問題vue實現星級評價效果實例詳解vue腳手架編寫試卷頁面功能vue.js實現表格合并示例代碼分分鐘玩轉vue.js組件(二)基于vue實現web端超大數據量表格的卡頓解決vue.js 點擊按鈕顯示/隱藏內容的實例代碼vue引用js文件的多種方式(推薦)簡單理解vue中props屬性vue之父子組件間通信實例講解(prvue元素的隱藏和顯示(v-show指令vue.js常用指令匯總(v-if、v-fovue props用法詳解(小結)vue 進階教程之v-model詳解使用vue實現圖片上傳的三種方式vue.js實戰之利用vue-router實現vue.js中的圖片引用路徑的方式vue 1.x 交互實現仿百度下拉列表示例vuex vue簡單使用知識點總結淺析vue項目中使用keep-alive步驟vuejs實現ready函數加載完之后執行某個函vue中使用protobuf的過程記錄vue router動態路由設置參數可選問題vue 父組件給子組件傳值子組件給父組件傳vue里如何主動銷毀keep-alive緩存的組件vue父組件調用子組件事件方法vue+jwt+springboot+ldap完成登錄認證的示
    免責聲明 - 關于我們 - 聯系我們 - 廣告聯系 - 友情鏈接 - 幫助中心 - 頻道導航
    Copyright © 2017 www.13333515.buzz All Rights Reserved
    3排列五开奖结果