vue如何实现observer和watcher源码解析

网友投稿 188 2023-06-04


vue如何实现observer和watcher源码解析

本文能帮你做什么?好奇vue双向绑定的同学,可以部分缓解好奇心,还可以帮你了解如何实现$watch。

前情回顾

我之前写了一篇没什么干货的文章,并且刨了一个大坑。

今天,打算来填一天,并再刨一个。

不过话说说回来了,看本文之前,如果不知道Object.defineProperty,还必须看看解析神奇的Object.defineProperty

本文将实现什么

正如上一篇许下的承诺一样,本文要实现一个$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 = observehHZNMSWhFr(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 vahttp://lue = 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组件学习教程进行学习。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:canvas实现爱心和彩虹雨效果
下一篇:SpringMVC表单标签使用详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~