这篇文章主要为大家详细介绍了vue如何实现observer和watcher源码的相关资料,分析vue的observe实现源码,聊聊如何一步一步实现$watch,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
本文能帮你做什么?好奇vue双向绑定的同学,可以部分缓解好奇心,还可以帮你了解如何实现$watch。
前情回顾
我之前写了一篇没什么干货的文章,并且刨了一个大坑。
今天,打算来填一天,并再刨一个。
不过话说说回来了,看本文之前,如果不知道Object.defineProperty,还必须看看解析神奇的Object.defineProperty
不得不感慨vue的作者,人长得帅,码写的也好,本文是根据作者源码,摘取出来的
本文将实现什么
正如上一篇许下的承诺一样,本文要实现一个$wacth
1
2
3
4
5
6
7
8
9
10
|
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。开始吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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方法
1
2
3
4
5
6
7
8
9
10
11
|
export
default
class Dep {
constructor() {
this
.subs = []
}
addSub(sub){
this
.subs.push(sub)
}
notify(){
this
.subs.forEach(sub=>sub.update())
}
}
|
所以,每次set函数,调用的时候,我们是不是应该,触发notify,对吧。所以我们把代码补充完整
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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”) 和 回调函数,这是最基本的,所以我们简单写写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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定义一个全局唯一的变量,跟着思路我们写一下
1
2
3
4
5
6
7
8
9
10
|
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(),代码补充一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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实例的属性,算了,不要理会我在说什么,直接看代码吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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组件学习教程进行学习。
转自:http://www.jb51.net/article/107927.htm