实现Vue的computed和watch

  • npx server

目录结构

1
2
3
4
5
6
├── computed.js
├── index.html
├── index.js
├── reactive.js
├── vue.js
└── watcher.js

index.html

1
<script type="module" src="./index.js"></script>

index.js

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
35
36
37
38
39
40
41
42
43
44
import Vue from './vue.js'

const vm = new Vue({
data() {
return {
a: 1,
b: 2
}
},
computed: {
total() {
console.log('computed is loading');
return this.a + this.b
}
},
watch: {
total(newValue, oldValue) {
console.log('watch total:', newValue, oldValue);
},
a(newValue, oldValue) {
console.log('watch a:', newValue, oldValue);
},
b(newValue, oldValue) {
console.log('watch b:', newValue, oldValue);
}
}
})

console.log(vm);

// 第一次执行 后面computed缓存
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);

vm.a = 100;
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);

vm.b = 200;
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);

vue.js

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { reactive } from "./reactive.js"
import Computed from './computed.js'
import Watcher from './watcher.js'

class Vue {
constructor(options) {
const { data, computed, watch } = options;
this.$data = data()

this.init(this, computed, watch)
}

init(vm, computed, watch) {
this.initData(vm)
const computedIns = this.initComputed(vm, computed)
const watchIns = this.initWatcher(vm, watch)

this.$computed = computedIns.update.bind(computedIns)
this.$watch = watchIns.invoke.bind(watchIns)
}

initData(vm) {
reactive(vm, (key, value) => {
// console.log(key, value);
}, (key, newValue, oldValue) => {
if (newValue == oldValue) return
// console.log(key, newValue, oldValue);
this.$computed(key, this.$watch)
this.$watch(key, newValue, oldValue)
})
}

initComputed(vm, computed) {
const computedIns = new Computed()
for (let key in computed) {
computedIns.addComputed(vm, computed, key)
}
return computedIns
}

initWatcher(vm, watch) {
const watchIns = new Watcher()

for (let key in watch) {
watchIns.addWatcher(vm, watch, key)
}
return watchIns
}

}

export default Vue;

reactive.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export function reactive(vm, __get__, __set__) {
const _data = vm.$data;

for (let key in _data) {
Object.defineProperty(vm, key, {
get() {
__get__(key, _data[key])
return _data[key]
},
set(newValue) {
const oldValue = _data[key]
_data[key] = newValue;
__set__(key, newValue, oldValue)
}
})
}

}

computed.js

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Computed {
constructor() {
this.computedData = [];
}

addComputed(vm, computed, key) {
const descriptor = Object.getOwnPropertyDescriptor(computed, key)
const descriptorFn = descriptor.value.get ? descriptor.value.get : descriptor.value
const value = descriptorFn.call(vm)
const get = descriptorFn.bind(vm)
const dep = this._colletDep(descriptorFn)

this._addComputedProp({
key,
value,
get,
dep
})

const dataItem = this.computedData.find(item => item.key === key)

Object.defineProperty(vm, key, {
get() {
return dataItem.value;
},
set(newValue) {
dataItem.value = dataItem.get()
}
})
}

update(key, watch) {
this.computedData.map(item => {
const dep = item.dep;
const _key = dep.find(el => el == key);

if (_key) {
const oldValue = item.value
item.value = item.get();
watch(item.key, item.value, oldValue)
}

})
}

_addComputedProp(computedProp) {
this.computedData.push(computedProp)
}

_colletDep(fn) {
const matched = fn.toString().match(/this\.(.+?)/g)
return matched.map(item => item.split('.')[1])
}
}

export default Computed

watcher.js

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
export default class Watcher {

constructor() {
this.watchers = []
}

addWatcher(vm, watcher, key) {
this.watchers.push({
key,
fn: watcher[key].bind(vm)
})
}

invoke(key, newValue, oldValue) {
this.watchers.map(item => {
if (item.key === key) {
item.fn(newValue, oldValue)
}
})
}

_addWatchProp(watchProp) {
this.watchers.push(watchProp)
}

}

仓库地址