https://segmentfault.com/a/1190000004384515javascript
本文能幫你作什麼? 。。好奇vue雙向綁定的同窗, 能夠部分緩解好奇心 還能夠幫你瞭解如何實現$watch
vue
我以前寫了一篇沒什麼乾貨的文章。。而且刨了一個大坑。。 今天。。打算來填一天。。並再刨一個。。哈哈 不過話說說回來了.看本文以前,, 若是不知道Object.defineProperty,還必須看看解析神奇的 Object.defineProperty 不得不感慨vue的做者,人長得帥,碼寫的也好。 本文是根據做者源碼,摘取出來的java
正如上一篇許下的承諾同樣,本文要實現一個 $wacth
git
const v = new Vue({ data:{ a:1, b:2 } }) v.$watch("a",()=>console.log("哈哈,$watch成功")) setTimeout(()=>{ v.a = 5 },2000) //打印 哈哈,$watch成功
爲了幫助你們理清思路。。咱們就作最簡單的實現。。只考慮對象不考慮數組github
思路:咱們知道Object.defineProperty的特性了, 咱們就利用它的set和get。。咱們將要observe的對象, 經過遞歸,將它全部的屬性,包括子屬性的屬性,都給加上set和get, 這樣的話,給這個對象的某個屬性賦值,就會觸發set。。嗯。。開始吧express
export default class Observer{ constructor(value) { this.value = value this.walk(value) } //遞歸。。讓每一個字屬性能夠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)//若是新賦值的值是個複雜類型。再遞歸它,加上set/get。。 } }) } export function observe (value, vm) { if (!value || typeof value !== 'object') { return } return new Observer(value) }
代碼很簡單,就給每一個屬性(包括子屬性)都加上get/set, 這樣的話,這個對象的,有任何賦值,就會觸發set方法。。 因此,咱們是否是應該寫一個消息-訂閱器呢?這樣的話, 一觸發set方法,咱們就發一個通知出來,而後,訂閱這個消息的, 就會怎樣?。。。對咯。。收到消息。。。觸發回調。segmentfault
很簡單,咱們維護一個數組,,這個數組,就放訂閱着,一旦觸發notify, 訂閱者就調用本身的update方法數組
export default class Dep { constructor() { this.subs = [] } addSub(sub){ this.subs.push(sub) } notify(){ this.subs.forEach(sub=>sub.update()) } }
因此,每次set函數,調用的時候,咱們是否是應該,觸發notify,對吧。因此 咱們把代碼補充完整閉包
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() } }) }
那麼問題來了。。誰是訂閱者。。對,是Watcher。。一旦 dep.notify()
就遍歷訂閱者,也就是Watcher,並調用他的update()
方法函數
咱們想象這個Watcher,應該用什麼東西。update方法,嗯這個毋庸置疑, 還有呢,
v.$watch("a",()=>console.log("哈哈,$watch成功"))
對錶達式(就是那個「a」) 和 回調函數,這是最基本的,因此咱們簡單寫寫
export default class Watcher { constructor(vm, expOrFn, cb) { this.cb = cb this.vm = vm //此處簡化.要區分fuction仍是expression,只考慮最簡單的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(){ //此處簡化。。要區分fuction仍是expression const value = this.vm._data[this.expOrFn] return value } }
那麼問題來了,咱們怎樣將經過addSub()
,將Watcher
加進去呢。 咱們發現var dep = new Dep()
處於閉包當中, 咱們又發現Watcher
的構造函數裏會調用this.get
因此,咱們能夠在上面動動手腳, 修改一下Object.defineProperty
的get
要調用的函數, 判斷是否是Watcher的構造函數調用,若是是,說明他就是這個屬性的訂閱者 果斷將他addSub()
中去,那問題來了, 我怎樣判斷他是Watcher
的this.get
調用的,而不是咱們普通調用的呢。 對,在Dep定義一個全局惟一的變量,跟着思路咱們寫一下
export default class Watcher { ....省略未改動代碼.... get(){ Dep.target = this //此處簡化。。要區分fuction仍是expression const value = this.vm._data[this.expOrFn] Dep.target = null return value } }
這樣的話,咱們只須要在Object.defineProperty
的get
要調用的函數裏, 判斷有沒有值,就知道究竟是Watcher 在get,仍是咱們本身在查看賦值,若是 是Watcher的話就addSub()
,代碼補充一下
export function defineReactive (obj, key, val) { var dep = new Dep() var childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>{ // 說明這是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
還差一步就大功告成了,咱們要把以上代碼配合Vue的$watch方法來用, 要watch Vue實例的屬性,算了,,不要理會我在說什麼,,直接看代碼吧
import Watcher from '../watcher' import {observe} from "../observer" export default class Vue { constructor (options={}) { //這裏簡化了。。其實要merge this.$options=options //這裏簡化了。。其實要區分的 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 } }) } }
很是簡單。。兩件事,observe本身的data,代理本身的data, 使訪問本身的屬性,就是訪問子data的屬性。。 截止到如今,在咱們只考慮最簡單狀況下。。整個流程終於跑通了。。確定會有 不少bug,本文主要目的是展現整個工做流,幫助讀者理解。。 代碼在https://github.com/georgebbbb..., 我是一萬個不想展現本身代碼。。由於不少槽點,還請見諒