<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從Vue源碼角度深挖Watch、Computed

          共 12878字,需瀏覽 26分鐘

           ·

          2020-07-27 19:04






          作者:Naice

          https://segmentfault.com/a/1190000023196603

          關(guān)注Vue中文社區(qū),每日精選好文

          這篇文章將帶大家全面理解vuewatchercomputeduser watcher,其實(shí)computeduser watcher都是基于Watcher來實(shí)現(xiàn)的,我們通過一個(gè)一個(gè)功能點(diǎn)去敲代碼,讓大家全面理解其中的實(shí)現(xiàn)原理和核心思想。所以這篇文章將實(shí)現(xiàn)以下這些功能點(diǎn):

          • 實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式
          • 基于渲染wather實(shí)現(xiàn)首次數(shù)據(jù)渲染到界面上
          • 數(shù)據(jù)依賴收集和更新
          • 實(shí)現(xiàn)數(shù)據(jù)更新觸發(fā)渲染watcher執(zhí)行,從而更新ui界面
          • 基于watcher實(shí)現(xiàn)computed
          • 基于watcher實(shí)現(xiàn)user watcher

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

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

          準(zhǔn)備工作

          首先我們準(zhǔn)備了一個(gè)index.html文件和一個(gè)vue.js文件,先看看index.html的代碼


          "en">

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


          ??"root">

          ??
          ??


          index.html里面分別有一個(gè)id是root的div節(jié)點(diǎn),這是跟節(jié)點(diǎn),然后在script標(biāo)簽里面,引入了vue.js,里面提供了Vue構(gòu)造函數(shù),然后就是實(shí)例化Vue,參數(shù)是一個(gè)對(duì)象,對(duì)象里面分別有data 和 render 函數(shù)。然后我們看看vue.js的代碼:

          function?Vue?(options)?{
          ??this._init(options)?//?初始化
          ??this.$mount()?//?執(zhí)行render函數(shù)
          }
          Vue.prototype._init?=?function?(options)?{
          ??const?vm?=?this
          ??vm.$options?=?options?//?把options掛載到this上
          ??if?(options.data)?{
          ????initState(vm)?//?數(shù)據(jù)響應(yīng)式
          ??}
          ??if?(options.computed)?{
          ????initComputed(vm)?//?初始化計(jì)算屬性
          ??}
          ??if?(options.watch)?{
          ????initWatch(vm)?//?初始化watch
          ??}
          }

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

          實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式

          要實(shí)現(xiàn)這些watcher首先去實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式,也就是要實(shí)現(xiàn)上面的initState(vm)這個(gè)函數(shù)。相信大家都很熟悉響應(yīng)式這些代碼,下面我直接貼上來。

          function?initState(vm)?{
          ??let?data?=?vm.$options.data;?//?拿到配置的data屬性值
          ??//?判斷data?是函數(shù)還是別的類型
          ??data?=?vm._data?=?typeof?data?===?'function'???data.call(vm,?vm)?:?data?||?{};
          ??const?keys?=?Object.keys(data);
          ??let?i?=?keys.length;
          ??while(i--)?{
          ????//?從this上讀取的數(shù)據(jù)全部攔截到this._data到里面讀取
          ????//?例如?this.name?等同于??this._data.name
          ????proxy(vm,?'_data',?keys[i]);
          ??}
          ??observe(data);?//?數(shù)據(jù)觀察
          }

          //?數(shù)據(jù)觀察函數(shù)
          function?observe(data)?{
          ??if?(typeof?data?!==?'object'?&&?data?!=?null)?{
          ????return;
          ??}
          ??return?new?Observer(data)
          }

          //?從this上讀取的數(shù)據(jù)全部攔截到this._data到里面讀取
          //?例如?this.name?等同于??this._data.name
          function?proxy(vm,?source,?key)?{
          ??Object.defineProperty(vm,?key,?{
          ????get()?{
          ??????return?vm[source][key]?//?this.name?等同于??this._data.name
          ????},
          ????set(newValue)?{
          ??????return?vm[source][key]?=?newValue
          ????}
          ??})
          }

          class?Observer{
          ??constructor(value)?{
          ????this.walk(value)?//?給每一個(gè)屬性都設(shè)置get?set
          ??}
          ??walk(data)?{
          ????let?keys?=?Object.keys(data);
          ????for?(let?i?=?0,?len?=?keys.length;?i???????let?key?=?keys[i]
          ??????let?value?=?data[key]
          ??????defineReactive(data,?key,?value)?//?給對(duì)象設(shè)置get?set
          ????}
          ??}
          }

          function?defineReactive(data,?key,?value)?{
          ??Object.defineProperty(data,?key,?{
          ????get()?{
          ??????return?value
          ????},
          ????set(newValue)?{
          ??????if?(newValue?==?value)?return
          ??????observe(newValue)?//?給新的值設(shè)置響應(yīng)式
          ??????value?=?newValue
          ????}
          ??})
          ??observe(value);?//?遞歸給數(shù)據(jù)設(shè)置get?set
          }

          重要的點(diǎn)都在注釋里面,主要核心就是給遞歸給data里面的數(shù)據(jù)設(shè)置getset,然后設(shè)置數(shù)據(jù)代理,讓 this.name 等同于 this._data.name。設(shè)置完數(shù)據(jù)觀察,我們就可以看到如下圖的數(shù)據(jù)了。

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

          ps: 數(shù)組的數(shù)據(jù)觀察大家自行去完善哈,這里重點(diǎn)講的是watcher的實(shí)現(xiàn)。

          首次渲染

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

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

          以上的代碼終于牽扯到我們Watcher這個(gè)主角了,這里其實(shí)就是我們的渲染wather,這里的目的是通過Watcher來實(shí)現(xiàn)執(zhí)行render函數(shù),從而把數(shù)據(jù)插入到root節(jié)點(diǎn)里面去。下面看最簡(jiǎn)單的Watcher實(shí)現(xiàn)

          let?wid?=?0
          class?Watcher?{
          ??constructor(vm,?exprOrFn,?cb,?options)?{
          ????this.vm?=?vm?//?把vm掛載到當(dāng)前的this上
          ????if?(typeof?exprOrFn?===?'function')?{
          ??????this.getter?=?exprOrFn?//?把exprOrFn掛載到當(dāng)前的this上,這里exprOrFn?等于?vm.$options.render
          ????}
          ????this.cb?=?cb?//?把cb掛載到當(dāng)前的this上
          ????this.options?=?options?//?把options掛載到當(dāng)前的this上
          ????this.id?=?wid++
          ????this.value?=?this.get()?//?相當(dāng)于運(yùn)行?vm.$options.render()
          ??}
          ??get()?{
          ????const?vm?=?this.vm
          ????let?value?=?this.getter.call(vm,?vm)?//?把this?指向到vm
          ????return?value
          ??}
          }

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

          數(shù)據(jù)依賴收集和更新

          首先數(shù)據(jù)收集,我們要有一個(gè)收集的地方,就是我們的Dep類,下面呢看看我們?nèi)ピ趺磳?shí)現(xiàn)這個(gè)Dep

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

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

          Dep收集的類是實(shí)現(xiàn)了,但是我們?cè)趺慈ナ占耍褪俏覀償?shù)據(jù)觀察的get里面實(shí)例化Dep然后讓Dep收集當(dāng)前的watcher。下面我們一步步來:

          • 1、在上面this.$mount()的代碼中,我們運(yùn)行了new Watcher(vm, vm.$options.render, () => {}, true),這時(shí)候我們就可以在Watcher里面執(zhí)行this.get(),然后執(zhí)行pushTarget(this),就可以執(zhí)行這句話Dep.target = watcher,把當(dāng)前的watcher掛載Dep.target上。下面看看我們?cè)趺磳?shí)現(xiàn)。
          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?已經(jīng)收集過相同的watcher?就不要重復(fù)收集了
          ????this.value?=?this.get()
          ??}
          ??get()?{
          ????const?vm?=?this.vm
          +???pushTarget(this)
          ????let?value?=?this.getter.call(vm,?vm)?//?執(zhí)行函數(shù)
          +???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是怎么來之后,然后上面代碼運(yùn)行了this.get(),相當(dāng)于運(yùn)行了vm.$options.render,在render里面回執(zhí)行this.name,這時(shí)候會(huì)觸發(fā)Object.defineProperty·get方法,我們?cè)诶锩婢涂梢宰鲂┮蕾囀占?dep.depend)了,如下代碼
          function?defineReactive(data,?key,?value)?{
          ??let?dep?=?new?Dep()
          ??Object.defineProperty(data,?key,?{
          ????get()?{
          +??????if?(Dep.target)?{?//?如果取值時(shí)有watcher
          +????????dep.depend()?//?讓watcher保存dep,并且讓dep?保存watcher,雙向保存
          +??????}
          ??????return?value
          ????},
          ????set(newValue)?{
          ??????if?(newValue?==?value)?return
          ??????observe(newValue)?//?給新的值設(shè)置響應(yīng)式
          ??????value?=?newValue
          +??????dep.notify()?//?通知渲染watcher去更新
          ????}
          ??})
          ??//?遞歸給數(shù)據(jù)設(shè)置get?set
          ??observe(value);
          }
          • 3、調(diào)用的dep.depend() 實(shí)際上是調(diào)用了 Dep.target.addDep(this), 此時(shí)Dep.target等于當(dāng)前的watcher,然后就會(huì)執(zhí)行
          addDep(dep)?{
          ??let?id?=?dep.id
          ??if?(!this.depsId.has(id))?{
          ????this.depsId.add(id)
          ????this.deps.push(dep)?//?當(dāng)前的watcher收集dep
          ????dep.addSub(this);?//?當(dāng)前的dep收集當(dāng)前的watcer
          ??}
          }

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

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

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

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

          運(yùn)行效果如下圖

          到這里我們渲染watcher就全部實(shí)現(xiàn)了。

          實(shí)現(xiàn)computed

          首先我們?cè)?code style>index.html里面配置一個(gè)computed,script標(biāo)簽的代碼就如下:

          const?root?=?document.querySelector('#root')
          var?vue?=?new?Vue({
          ??data()?{
          ????return?{
          ??????name:?'張三',
          ??????age:?10
          ????}
          ??},
          ??computed:?{
          ????info()?{
          ??????return?this.name?+?this.age
          ????}
          ??},
          ??render()?{
          ????root.innerHTML?=?`${this.name}----${this.age}----${this.info}`
          ??}
          })
          function?changeData()?{
          ??vue.name?=?'李四'
          ??vue.age?=?20
          }

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

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

          if?(options.computed)?{
          ??//?初始化計(jì)算屬性
          ??initComputed(vm)
          }

          我們現(xiàn)在就實(shí)現(xiàn)這個(gè)initComputed,代碼如下

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

          大家都知道computed是有緩存的,所以創(chuàng)建watcher的時(shí)候,會(huì)傳一個(gè)配置{ lazy: true },同時(shí)也可以區(qū)分這是computed watcher,然后到watcer里面接收到這個(gè)對(duì)象

          class?Watcher?{
          ??constructor(vm,?exprOrFn,?cb,?options)?{
          ????this.vm?=?vm
          ????if?(typeof?exprOrFn?===?'function')?{
          ??????this.getter?=?exprOrFn
          ????}
          +????if?(options)?{
          +??????this.lazy?=?!!options.lazy?//?為computed?設(shè)計(jì)的
          +????}?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創(chuàng)建watcher的時(shí)候是不會(huì)指向this.get的。只有在render函數(shù)里面有才執(zhí)行。

          現(xiàn)在在render函數(shù)通過this.info還不能讀取到值,因?yàn)槲覀冞€沒有掛載到vm上面,上面defineComputed(vm, key, userDef)這個(gè)函數(shù)功能就是讓computed掛載到vm上面。下面我們實(shí)現(xiàn)一下。

          //?設(shè)置comoputed的?set個(gè)set
          function?defineComputed(vm,?key,?userDef)?{
          ??let?getter?=?null
          ??//?判斷是函數(shù)還是對(duì)象
          ??if?(typeof?userDef?===?'function')?{
          ????getter?=?createComputedGetter(key)
          ??}?else?{
          ????getter?=?userDef.get
          ??}
          ??Object.defineProperty(vm,?key,?{
          ????enumerable:?true,
          ????configurable:?true,
          ????get:?getter,
          ????set:?function()?{}?//?又偷懶,先不考慮set情況哈,自己去看源碼實(shí)現(xiàn)一番也是可以的
          ??})
          }
          //?創(chuàng)建computed函數(shù)
          function?createComputedGetter(key)?{
          ??return?function?computedGetter()?{
          ????const?watcher?=?this._computedWatchers[key]
          ????if?(watcher)?{
          ??????if?(watcher.dirty)?{//?給computed的屬性添加訂閱watchers
          ????????watcher.evaluate()
          ??????}
          ??????//?把渲染watcher?添加到屬性的訂閱里面去,這很關(guān)鍵
          ??????if?(Dep.target)?{
          ????????watcher.depend()
          ??????}
          ??????return?watcher.value
          ????}
          ??}
          }

          上面代碼有看到在watcher中調(diào)用了watcher.evaluate()watcher.depend(),然后去watcher里面實(shí)現(xiàn)這兩個(gè)方法,下面直接看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?設(shè)計(jì)的
          ????}?else?{
          ??????this.lazy?=?false
          ????}
          ????this.dirty?=?this.lazy
          ????this.cb?=?cb
          ????this.options?=?options
          ????this.id?=?wId++
          ????this.deps?=?[]
          ????this.depsId?=?new?Set()?//?dep?已經(jīng)收集過相同的watcher?就不要重復(fù)收集了
          ????this.value?=?this.lazy???undefined?:?this.get()
          ??}
          ??get()?{
          ????const?vm?=?this.vm
          ????pushTarget(this)
          ????//?執(zhí)行函數(shù)
          ????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()
          ????}
          ??}
          ??//?執(zhí)行g(shù)et,并且?this.dirty?=?false
          +??evaluate()?{
          +????this.value?=?this.get()
          +????this.dirty?=?false
          +??}
          ??//?所有的屬性收集當(dāng)前的watcer
          +??depend()?{
          +????let?i?=?this.deps.length
          +????while(i--)?{
          +??????this.deps[i].depend()
          +????}
          +??}
          }

          代碼都實(shí)現(xiàn)王完成之后,我們說下流程,

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

          實(shí)現(xiàn)了之后我們看看實(shí)現(xiàn)效果:

          這里conputed的對(duì)象set配置沒有實(shí)現(xiàn),大家可以自己看看源碼

          watch實(shí)現(xiàn)

          先在script標(biāo)簽配置watch配置如下代碼:

          const?root?=?document.querySelector('#root')
          var?vue?=?new?Vue({
          ??data()?{
          ????return?{
          ??????name:?'張三',
          ??????age:?10
          ????}
          ??},
          ??computed:?{
          ????info()?{
          ??????return?this.name?+?this.age
          ????}
          ??},
          ??watch:?{
          ????name(oldValue,?newValue)?{
          ??????console.log(oldValue,?newValue)
          ????}
          ??},
          ??render()?{
          ????root.innerHTML?=?`${this.name}----${this.age}----${this.info}`
          ??}
          })
          function?changeData()?{
          ??vue.name?=?'李四'
          ??vue.age?=?20
          }

          知道了computed實(shí)現(xiàn)之后,自定義watch實(shí)現(xiàn)很簡(jiǎn)單,下面直接實(shí)現(xiàn)initWatch

          function?initWatch(vm)?{
          ??let?watch?=?vm.$options.watch
          ??for?(let?key?in?watch)?{
          ????const?handler?=?watch[key]
          ????new?Watcher(vm,?key,?handler,?{?user:?true?})
          ??}
          }

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

          let?wId?=?0
          class?Watcher?{
          ??constructor(vm,?exprOrFn,?cb,?options)?{
          ????this.vm?=?vm
          ????if?(typeof?exprOrFn?===?'function')?{
          ??????this.getter?=?exprOrFn
          ????}?else?{
          +??????this.getter?=?parsePath(exprOrFn)?//?user?watcher?
          ????}
          ????if?(options)?{
          ??????this.lazy?=?!!options.lazy?//?為computed?設(shè)計(jì)的
          +??????this.user?=?!!options.user?//?為user?wather設(shè)計(jì)的
          ????}?else?{
          +??????this.user?=?this.lazy?=?false
          ????}
          ????this.dirty?=?this.lazy
          ????this.cb?=?cb
          ????this.options?=?options
          ????this.id?=?wId++
          ????this.deps?=?[]
          ????this.depsId?=?new?Set()?//?dep?已經(jīng)收集過相同的watcher?就不要重復(fù)收集了
          ????this.value?=?this.lazy???undefined?:?this.get()
          ??}
          ??get()?{
          ????const?vm?=?this.vm
          ????pushTarget(this)
          ????//?執(zhí)行函數(shù)
          ????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.run()
          ????}
          ??}
          ??//?執(zhí)行g(shù)et,并且?this.dirty?=?false
          ??evaluate()?{
          ????this.value?=?this.get()
          ????this.dirty?=?false
          ??}
          ??//?所有的屬性收集當(dāng)前的watcer
          ??depend()?{
          ????let?i?=?this.deps.length
          ????while(i--)?{
          ??????this.deps[i].depend()
          ????}
          ??}
          +??run?()?{
          +????const?value?=?this.get()
          +????const?oldValue?=?this.value
          +????this.value?=?value
          ????//?執(zhí)行cb
          +????if?(this.user)?{
          +??????try{
          +????????this.cb.call(this.vm,?value,?oldValue)
          +??????}?catch(error)?{
          +????????console.error(error)
          +??????}
          +????}?else?{
          +??????this.cb?&&?this.cb.call(this.vm,?oldValue,?value)
          +????}
          +??}
          }
          function?parsePath?(path)?{
          ??const?segments?=?path.split('.')
          ??return?function?(obj)?{
          ????for?(let?i?=?0;?i???????if?(!obj)?return
          ??????obj?=?obj[segments[i]]
          ????}
          ????return?obj
          ??}
          }

          最后看看效果

          當(dāng)然很多配置沒有實(shí)現(xiàn),比如說options.immediate 或者options.deep等配置都沒有實(shí)現(xiàn)。篇幅太長(zhǎng)了。自己也懶~~~ 完結(jié)撒花

          詳細(xì)代碼:https://github.com/naihe138/write-vue




          推薦閱讀




          我的公眾號(hào)能帶來什么價(jià)值?(文末有送書規(guī)則,一定要看)

          每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長(zhǎng)文建議收藏)

          為什么現(xiàn)在面試總是面試造火箭?

          瀏覽 56
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  aⅴ在线视频 | 国产看真人毛片爱做A片 | 欧美第十页 | 色婷婷五月网 | 成人一级毛片 |