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

首頁 > 編程 > JavaScript > 正文

vue如何實現observer和watcher源碼解析

2019-11-19 17:13:19
字體:
來源:轉載
供稿:網友

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

前情回顧

我之前寫了一篇沒什么干貨的文章,并且刨了一個大坑。
今天,打算來填一天,并再刨一個。

不過話說說回來了,看本文之前,如果不知道Object.defineProperty,還必須看看解析神奇的Object.defineProperty
不得不感慨vue的作者,人長得帥,碼寫的也好,本文是根據作者源碼,摘取出來的

本文將實現什么

正如上一篇許下的承諾一樣,本文要實現一個$wacth

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

為了幫助大家理清思路。。我們就做最簡單的實現。。只考慮對象不考慮數組

1. 實現 observer

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

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方法,我們就發一個通知出來,然后,訂閱這個消息的,就會怎樣?對咯。、收到消息、觸發回調。

2. 消息-訂閱器

很簡單,我們維護一個數組,,這個數組,就放訂閱著,一旦觸發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()方法

3. 實現一個Watcher
我們想象這個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

4. 實現一個 Vue

還差一步就大功告成了,我們要把以上代碼配合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...,

我是一萬個不想展示自己代碼,因為很多槽點,還請見諒

下一篇,有兩個方向,將聊一聊如何實現雙向綁定,或者是如何watch數組。

關于vue2.0的新文章

100行代碼,理解和分析vue2.0的響應式架構

本文已被整理到了《Vue.js前端組件學習教程》,歡迎大家學習閱讀。

關于vue.js組件的教程,請大家點擊專題vue.js組件學習教程進行學習。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 海原县| 韩城市| 勐海县| 万载县| 琼中| 东光县| 新津县| 定州市| 东乡| 江门市| 当阳市| 大渡口区| 克东县| 盱眙县| 天祝| 衡东县| 潍坊市| 盐亭县| 德庆县| 井冈山市| 通州市| 德钦县| 拜泉县| 当涂县| 玉林市| 凤城市| 南溪县| 榆林市| 华阴市| 湘阴县| 桦川县| 怀化市| 荥经县| 克什克腾旗| 哈巴河县| 治多县| 扎鲁特旗| 师宗县| 灵台县| 盈江县| 鄂托克旗|