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

首頁(yè) > 開發(fā) > JS > 正文

vue如何實(shí)現(xiàn)observer和watcher源碼解析

2024-05-06 16:35:39
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

本文能幫你做什么?好奇vue雙向綁定的同學(xué),可以部分緩解好奇心,還可以幫你了解如何實(shí)現(xiàn)$watch。

前情回顧

我之前寫了一篇沒(méi)什么干貨的文章,并且刨了一個(gè)大坑。
今天,打算來(lái)填一天,并再刨一個(gè)。

不過(guò)話說(shuō)說(shuō)回來(lái)了,看本文之前,如果不知道Object.defineProperty,還必須看看解析神奇的Object.defineProperty
不得不感慨vue的作者,人長(zhǎng)得帥,碼寫的也好,本文是根據(jù)作者源碼,摘取出來(lái)的

本文將實(shí)現(xiàn)什么

正如上一篇許下的承諾一樣,本文要實(shí)現(xiàn)一個(gè)$wacth

const v = new Vue({ data:{ a:1, b:2 }})v.$watch("a",()=>console.log("哈哈,$watch成功"))setTimeout(()=>{ v.a = 5},2000) //打印 哈哈,$watch成功

為了幫助大家理清思路。。我們就做最簡(jiǎn)單的實(shí)現(xiàn)。。只考慮對(duì)象不考慮數(shù)組

1. 實(shí)現(xiàn) observer

思路:我們知道Object.defineProperty的特性了,我們就利用它的set和get。我們將要observe的對(duì)象,通過(guò)遞歸,將它所有的屬性,包括子屬性的屬性,都給加上set和get。這樣的話,給這個(gè)對(duì)象的某個(gè)屬性賦值,就會(huì)觸發(fā)set。開始吧

export default class Observer{ constructor(value) { this.value = value this.walk(value) } //遞歸。。讓每個(gè)字屬性可以observe walk(value){ Object.keys(value).forEach(key=>this.convert(key,value[key])) } convert(key, val){ defineReactive(this.value, key, val) }}export function defineReactive (obj, key, val) { var childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>val, set:newVal=> {    childOb = observe(newVal)//如果新賦值的值是個(gè)復(fù)雜類型。再遞歸它,加上set/get。。  } })}export function observe (value, vm) { if (!value || typeof value !== 'object') { return } return new Observer(value)}

代碼很簡(jiǎn)單,就給每個(gè)屬性(包括子屬性)都加上get/set,這樣的話,這個(gè)對(duì)象的,有任何賦值,就會(huì)觸發(fā)set方法。。
所以,我們是不是應(yīng)該寫一個(gè)消息-訂閱器呢?

這樣的話,一觸發(fā)set方法,我們就發(fā)一個(gè)通知出來(lái),然后,訂閱這個(gè)消息的,就會(huì)怎樣?對(duì)咯。、收到消息、觸發(fā)回調(diào)。

2. 消息-訂閱器

很簡(jiǎn)單,我們維護(hù)一個(gè)數(shù)組,,這個(gè)數(shù)組,就放訂閱著,一旦觸發(fā)notify,訂閱者就調(diào)用自己的update方法

export default class Dep { constructor() { this.subs = [] } addSub(sub){ this.subs.push(sub) } notify(){ this.subs.forEach(sub=>sub.update()) }}

所以,每次set函數(shù),調(diào)用的時(shí)候,我們是不是應(yīng)該,觸發(fā)notify,對(duì)吧。所以我們把代碼補(bǔ)充完整

 export function defineReactive (obj, key, val) {  var dep = new Dep()  var childOb = observe(val)  Object.defineProperty(obj, key, {  enumerable: true,  configurable: true,  get: ()=>val,  set:newVal=> {   var value = val   if (newVal === value) {   return   }   val = newVal   childOb = observe(newVal)   dep.notify()  }  }) }

那么問(wèn)題來(lái)了。誰(shuí)是訂閱者。對(duì),是Watcher。一旦 dep.notify()就遍歷訂閱者,也就是Watcher,并調(diào)用他的update()方法

3. 實(shí)現(xiàn)一個(gè)Watcher
我們想象這個(gè)Watcher,應(yīng)該用什么東西。update方法,嗯這個(gè)毋庸置疑,還有呢。

v.$watch("a",()=>console.log("哈哈,$watch成功"))

對(duì)表達(dá)式(就是那個(gè)“a”) 和 回調(diào)函數(shù),這是最基本的,所以我們簡(jiǎn)單寫寫

export default class Watcher { constructor(vm, expOrFn, cb) { this.cb = cb this.vm = vm //此處簡(jiǎn)化.要區(qū)分fuction還是expression,只考慮最簡(jiǎn)單的expression this.expOrFn = expOrFn this.value = this.get() } update(){ this.run() } run(){ const value = this.get() if(value !==this.value){  this.value = value  this.cb.call(this.vm) } } get(){ //此處簡(jiǎn)化。。要區(qū)分fuction還是expression const value = this.vm._data[this.expOrFn] return value }}

那么問(wèn)題來(lái)了,我們?cè)鯓訉⑼ㄟ^(guò)addSub(),Watcher加進(jìn)去呢。
我們發(fā)現(xiàn)var dep = new Dep() 處于閉包當(dāng)中,我們又發(fā)現(xiàn)Watcher的構(gòu)造函數(shù)里會(huì)調(diào)用this.get,所以,我們可以在上面動(dòng)動(dòng)手腳,修改一下Object.defineProperty的get要調(diào)用的函數(shù),判斷是不是Watcher的構(gòu)造函數(shù)調(diào)用,如果是,說(shuō)明他就是這個(gè)屬性的訂閱者,果斷將他addSub()中去,那問(wèn)題來(lái)了?
我怎樣判斷他是Watcher的this.get調(diào)用的,而不是我們普通調(diào)用的呢

對(duì),在Dep定義一個(gè)全局唯一的變量,跟著思路我們寫一下

export default class Watcher { ....省略未改動(dòng)代碼.... get(){ Dep.target = this //此處簡(jiǎn)化。。要區(qū)分fuction還是expression const value = this.vm._data[this.expOrFn] Dep.target = null return value }}

這樣的話,我們只需要在Object.defineProperty的get要調(diào)用的函數(shù)里,判斷有沒(méi)有值,就知道到底是Watcher 在get,還是我們自己在查看賦值,如果是Watcher的話就addSub(),代碼補(bǔ)充一下

export function defineReactive (obj, key, val) { var dep = new Dep() var childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>{  // 說(shuō)明這是watch 引起的  if(Dep.target){  dep.addSub(Dep.target)  }  return val }, set:newVal=> {  var value = val  if (newVal === value) {  return  }  val = newVal  childOb = observe(newVal)  dep.notify() } })}

最后不要忘記,在Dep.js中加上這么一句

Dep.target = null

4. 實(shí)現(xiàn)一個(gè) Vue

還差一步就大功告成了,我們要把以上代碼配合Vue的$watch方法來(lái)用,要watch Vue實(shí)例的屬性,算了,不要理會(huì)我在說(shuō)什么,直接看代碼吧

 

import Watcher from '../watcher'import {observe} from "../observer"export default class Vue { constructor (options={}) { //這里簡(jiǎn)化了。。其實(shí)要merge this.$options=options //這里簡(jiǎn)化了。。其實(shí)要區(qū)分的 let data = this._data=this.$options.data Object.keys(data).forEach(key=>this._proxy(key)) observe(data,this) } $watch(expOrFn, cb, options){ new Watcher(this, expOrFn, cb) } _proxy(key) { var 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  } }) }}

非常簡(jiǎn)單。兩件事,observe自己的data,代理自己的data,使訪問(wèn)自己的屬性,就是訪問(wèn)子data的屬性。。
截止到現(xiàn)在,在我們只考慮最簡(jiǎn)單情況下,整個(gè)流程終于跑通了。肯定會(huì)有很多bug,本文主要目的是展示整個(gè)工作流,幫助讀者理解。
代碼在https://github.com/georgebbbb...,

我是一萬(wàn)個(gè)不想展示自己代碼,因?yàn)楹芏嗖埸c(diǎn),還請(qǐng)見諒

下一篇,有兩個(gè)方向,將聊一聊如何實(shí)現(xiàn)雙向綁定,或者是如何watch數(shù)組。

關(guān)于vue2.0的新文章

100行代碼,理解和分析vue2.0的響應(yīng)式架構(gòu)

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持VeVb武林網(wǎng)。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到JavaScript/Ajax教程頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 望都县| 永吉县| 仙居县| 平江县| 仁怀市| 巨鹿县| 花垣县| 通榆县| 宝丰县| 台北县| 江源县| 九江县| 德阳市| 宜宾市| 乌鲁木齐县| 儋州市| 阿鲁科尔沁旗| 嘉禾县| 田林县| 桐城市| 棋牌| 缙云县| 班玛县| 响水县| 昌都县| 溧阳市| 兴国县| 独山县| 建始县| 喀什市| 崇州市| 宣汉县| 秭归县| 常宁市| 丰镇市| 太仆寺旗| 古浪县| 十堰市| 同心县| 阿拉善右旗| 定结县|