日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

怎么通过id渲染页面_完全理解Vue的渲染watcher、computed和user watcher

發布時間:2025/3/15 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 怎么通过id渲染页面_完全理解Vue的渲染watcher、computed和user watcher 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:Naice

https://segmentfault.com/a/1190000023196603

這篇文章將帶大家全面理解vue的watcher、computed和user watcher,其實computed和user watcher都是基于Watcher來實現的,我們通過一個一個功能點去敲代碼,讓大家全面理解其中的實現原理和核心思想。所以這篇文章將實現以下這些功能點:

  • 實現數據響應式
  • 基于渲染wather實現首次數據渲染到界面上
  • 數據依賴收集和更新
  • 實現數據更新觸發渲染watcher執行,從而更新ui界面
  • 基于watcher實現computed
  • 基于watcher實現user watcher

廢話不要多說,先看下面的最終例子。

例子看完之后我們就直接開工了。

準備工作

首先我們準備了一個index.html文件和一個vue.js文件,先看看index.html的代碼

html>
"en">

??"UTF-8">
??全面理解vue的渲染watcher、computed和user?atcher


??"root">
??
??


index.html里面分別有一個id是root的div節點,這是跟節點,然后在script標簽里面,引入了vue.js,里面提供了Vue構造函數,然后就是實例化Vue,參數是一個對象,對象里面分別有data 和 render 函數。然后我們看看vue.js的代碼:

function?Vue?(options)?{

vue.js代碼里面就是執行this._init()和this.$mount(),this._init的方法就是對我們的傳進來的配置進行各種初始化,包括數據初始化initState(vm)、計算屬性初始化initComputed(vm)、自定義watch初始化initWatch(vm)。this.$mount方法把render函數渲染到頁面中去、這些方法我們后面都寫到,先讓讓大家了解整個代碼結構。下面我們正式去填滿我們上面寫的這些方法。

實現數據響應式

要實現這些watcher首先去實現數據響應式,也就是要實現上面的initState(vm)這個函數。相信大家都很熟悉響應式這些代碼,下面我直接貼上來。

function?initState(vm)?{

重要的點都在注釋里面,主要核心就是給遞歸給data里面的數據設置get和set,然后設置數據代理,讓 this.name 等同于 this._data.name。設置完數據觀察,我們就可以看到如下圖的數據了。

console.log(vue.name)?//?張三
console.log(vue.age)?//?10

ps: 數組的數據觀察大家自行去完善哈,這里重點講的是watcher的實現。

首次渲染

數據觀察搞定了之后,我們就可以把render函數渲染到我們的界面上了。在Vue里面我們有一個this.$mount()函數,所以要實現Vue.prototype.$mount函數:

//?掛載方法
Vue.prototype.$mount?=?function?()?{
??const?vm?=?this
??new?Watcher(vm,?vm.$options.render,?()?=>?{},?true)
}

以上的代碼終于牽扯到我們Watcher這個主角了,這里其實就是我們的渲染wather,這里的目的是通過Watcher來實現執行render函數,從而把數據插入到root節點里面去。下面看最簡單的Watcher實現

let?wid?=?0

通過上面的一頓操作,終于在render中終于可以通過this.name 讀取到data的數據了,也可以插入到root.innerHTML中去。階段性的工作我們完成了。如下圖,完成的首次渲染??

數據依賴收集和更新

首先數據收集,我們要有一個收集的地方,就是我們的Dep類,下面呢看看我們去怎么實現這個Dep。

//?依賴收集
let?dId?=?0
class?Dep{
??constructor()?{
????this.id?=?dId++?//?每次實例化都生成一個id
????this.subs?=?[]?//?讓這個dep實例收集watcher
??}
??depend()?{
????//?Dep.target?就是當前的watcher
????if?(Dep.target)?{
??????Dep.target.addDep(this)?//?讓watcher,去存放dep,然后里面dep存放對應的watcher,兩個是多對多的關系
????}
??}
??notify()?{
????//?觸發更新
????this.subs.forEach(watcher?=>?watcher.update())
??}
??addSub(watcher)?{
????this.subs.push(watcher)
??}
}

let?stack?=?[]
//?push當前watcher到stack?中,并記錄當前watcer
function?pushTarget(watcher)?{
??Dep.target?=?watcher
??stack.push(watcher)
}
//?運行完之后清空當前的watcher
function?popTarget()?{
??stack.pop()
??Dep.target?=?stack[stack.length?-?1]
}

Dep收集的類是實現了,但是我們怎么去收集了,就是我們數據觀察的get里面實例化Dep然后讓Dep收集當前的watcher。下面我們一步步來:

  • 1、在上面this.$mount()的代碼中,我們運行了new Watcher(vm, vm.$options.render, () => {}, true),這時候我們就可以在Watcher里面執行this.get(),然后執行pushTarget(this),就可以執行這句話Dep.target = watcher,把當前的watcher掛載Dep.target上。下面看看我們怎么實現。
class?Watcher?{
??constructor(vm,?exprOrFn,?cb,?options)?{
????this.vm?=?vm
????if?(typeof?exprOrFn?===?'function')?{
??????this.getter?=?exprOrFn
????}
????this.cb?=?cb
????this.options?=?options
????this.id?=?wid++
????this.id?=?wId++
+????this.deps?=?[]
+????this.depsId?=?new?Set()?//?dep?已經收集過相同的watcher?就不要重復收集了
????this.value?=?this.get()
??}
??get()?{
????const?vm?=?this.vm
+???pushTarget(this)
????let?value?=?this.getter.call(vm,?vm)?//?執行函數
+???popTarget()
????return?value
??}
+??addDep(dep)?{
+????let?id?=?dep.id
+????if?(!this.depsId.has(id))?{
+??????this.depsId.add(id)
+??????this.deps.push(dep)
+??????dep.addSub(this);
+????}
+??}
+??update(){
+????this.get()
+??}
}
  • 2、知道Dep.target是怎么來之后,然后上面代碼運行了this.get(),相當于運行了vm.$options.render,在render里面回執行this.name,這時候會觸發Object.defineProperty·get方法,我們在里面就可以做些依賴收集(dep.depend)了,如下代碼
function?defineReactive(data,?key,?value)?{
  • 3、調用的dep.depend() 實際上是調用了 Dep.target.addDep(this), 此時Dep.target等于當前的watcher,然后就會執行
addDep(dep)?{
??let?id?=?dep.id
??if?(!this.depsId.has(id))?{
????this.depsId.add(id)
????this.deps.push(dep)?//?當前的watcher收集dep
????dep.addSub(this);?//?當前的dep收集當前的watcer
??}
}

這里雙向保存有點繞,大家可以好好去理解一下。下面我們看看收集后的des是怎么樣子的。

  • 4、數據更新,調用this.name = '李四'的時候回觸發Object.defineProperty.set方法,里面直接調用dep.notify(),然后循環調用所有的watcer.update方法更新所有watcher,例如:這里也就是重新執行vm.$options.render方法。

有了依賴收集個數據更新,我們也在index.html增加修改data屬性的定時方法:

//?index.html
"changeData()">改變name和age
//?-----
//?.....省略代碼
function?changeData()?{
??vue.name?=?'李四'
??vue.age?=?20
}

運行效果如下圖

到這里我們渲染watcher就全部實現了。

實現computed

首先我們在index.html里面配置一個computed,script標簽的代碼就如下:

'#root')

上面的代碼,注意computed是在render里面使用了。

在vue.js中,之前寫了下面這行代碼。

if?(options.computed)?{

我們現在就實現這個initComputed,代碼如下

//?初始化computed
function?initComputed(vm)?{
??const?computed?=?vm.$options.computed?//?拿到computed配置
??const?watchers?=?vm._computedWatchers?=?Object.create(null)?//?給當前的vm掛載_computedWatchers屬性,后面會用到
??//?循環computed每個屬性
??for?(const?key?in?computed)?{
????const?userDef?=?computed[key]
????//?判斷是函數還是對象
????const?getter?=?typeof?userDef?===?'function'???userDef?:?userDef.get
????//?給每一個computed創建一個computed?watcher?注意{?lazy:?true?}
????//?然后掛載到vm._computedWatchers對象上
????watchers[key]?=?new?Watcher(vm,?getter,?()?=>?{},?{?lazy:?true?})
????if?(!(key?in?vm))?{
??????defineComputed(vm,?key,?userDef)
????}
??}
}

大家都知道computed是有緩存的,所以創建watcher的時候,會傳一個配置{ lazy: true },同時也可以區分這是computed watcher,然后到watcer里面接收到這個對象

class?Watcher?{
??constructor(vm,?exprOrFn,?cb,?options)?{
????this.vm?=?vm
????if?(typeof?exprOrFn?===?'function')?{
??????this.getter?=?exprOrFn
????}
+????if?(options)?{
+??????this.lazy?=?!!options.lazy?//?為computed?設計的
+????}?else?{
+??????this.lazy?=?false
+????}
+????this.dirty?=?this.lazy
????this.cb?=?cb
????this.options?=?options
????this.id?=?wId++
????this.deps?=?[]
????this.depsId?=?new?Set()
+????this.value?=?this.lazy???undefined?:?this.get()
??}
??//?省略很多代碼
}

從上面這句this.value = this.lazy ? undefined : this.get()代碼可以看到,computed創建watcher的時候是不會指向this.get的。只有在render函數里面有才執行。

現在在render函數通過this.info還不能讀取到值,因為我們還沒有掛載到vm上面,上面defineComputed(vm, key, userDef)這個函數功能就是讓computed掛載到vm上面。下面我們實現一下。

set個

上面代碼有看到在watcher中調用了watcher.evaluate()和watcher.depend(),然后去watcher里面實現這兩個方法,下面直接看watcher的完整代碼。

class?Watcher?{
??constructor(vm,?exprOrFn,?cb,?options)?{
????this.vm?=?vm
????if?(typeof?exprOrFn?===?'function')?{
??????this.getter?=?exprOrFn
????}
????if?(options)?{
??????this.lazy?=?!!options.lazy?//?為computed?設計的
????}?else?{
??????this.lazy?=?false
????}
????this.dirty?=?this.lazy
????this.cb?=?cb
????this.options?=?options
????this.id?=?wId++
????this.deps?=?[]
????this.depsId?=?new?Set()?//?dep?已經收集過相同的watcher?就不要重復收集了
????this.value?=?this.lazy???undefined?:?this.get()
??}
??get()?{
????const?vm?=?this.vm
????pushTarget(this)
????//?執行函數
????let?value?=?this.getter.call(vm,?vm)
????popTarget()
????return?value
??}
??addDep(dep)?{
????let?id?=?dep.id
????if?(!this.depsId.has(id))?{
??????this.depsId.add(id)
??????this.deps.push(dep)
??????dep.addSub(this);
????}
??}
??update(){
????if?(this.lazy)?{
??????this.dirty?=?true
????}?else?{
??????this.get()
????}
??}
??//?執行get,并且?this.dirty?=?false
+??evaluate()?{
+????this.value?=?this.get()
+????this.dirty?=?false
+??}
??//?所有的屬性收集當前的watcer
+??depend()?{
+????let?i?=?this.deps.length
+????while(i--)?{
+??????this.deps[i].depend()
+????}
+??}
}

代碼都實現王完成之后,我們說下流程,

  • 1、首先在render函數里面會讀取this.info,這個會觸發createComputedGetter(key)中的computedGetter(key);
  • 2、然后會判斷watcher.dirty,執行watcher.evaluate();
  • 3、進到watcher.evaluate(),才真想執行this.get方法,這時候會執行pushTarget(this)把當前的computed watcher push到stack里面去,并且把Dep.target 設置成當前的computed watcher`;
  • 4、然后運行this.getter.call(vm, vm) 相當于運行computed的info: function() { return this.name + this.age },這個方法;
  • 5、info函數里面會讀取到this.name,這時候就會觸發數據響應式Object.defineProperty.get的方法,這里name會進行依賴收集,把watcer收集到對應的dep上面;并且返回name = '張三'的值,age收集同理;
  • 6、依賴收集完畢之后執行popTarget(),把當前的computed watcher從棧清除,返回計算后的值('張三+10'),并且this.dirty = false;
  • 7、watcher.evaluate()執行完畢之后,就會判斷Dep.target 是不是true,如果有就代表還有渲染watcher,就執行watcher.depend(),然后讓watcher里面的deps都收集渲染watcher,這就是雙向保存的優勢。
  • 8、此時name都收集了computed watcher 和 渲染watcher。那么設置name的時候都會去更新執行watcher.update()
  • 9、如果是computed watcher的話不會重新執行一遍只會把this.dirty 設置成 true,如果數據變化的時候再執行watcher.evaluate()進行info更新,沒有變化的的話this.dirty 就是false,不會執行info方法。這就是computed緩存機制。

實現了之后我們看看實現效果:

這里conputed的對象set配置沒有實現,大家可以自己看看源碼

watch實現

先在script標簽配置watch配置如下代碼:

'#root')

知道了computed實現之后,自定義watch實現很簡單,下面直接實現initWatch

function?initWatch(vm)?{

然后修改一下Watcher,直接看Wacher的完整代碼。

let?wId?=?0

最后看看效果

當然很多配置沒有實現,比如說options.immediate 或者options.deep等配置都沒有實現。篇幅太長了。自己也懶~~~ 完結撒花

詳細代碼:https://github.com/naihe138/write-vue

感謝 · 轉發歡迎大家留言

好文章,我在看??

總結

以上是生活随笔為你收集整理的怎么通过id渲染页面_完全理解Vue的渲染watcher、computed和user watcher的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。