基本
这里实现的是vue中的data
computed
watch
,不涉及其他的功能。使用的是es6的类及方法。不是纯es5。
data
vue
中data
可以写成对象,也可以写成函数形式。但是由于组件的复用性,防止数据出错,这里直接使用了函数形式。
即,默认用户书写data为:
data(){
return {
a:1,
b:1
}
}
在vue类中,可以直接 使用 this.$data = data()
将数据挂载到vm
实例上。
Computed
这里定义一个Computed
类
其中实现了几个必须的方法:
· addComputed 对监听数据进行预处理
· upData 更新监听的数据 (供外部调用)
· _addComputedProp 添加新数据
· _collectionDep 提取监听的变量名
里面使用 Object.defineProperty
对数据监听,调用时直接返回值,设置值时就重新计算,这就实现了不改变值就不重新计算的功能
class Computed {
constructor() {
// 存储监听的数据
this.computedData = []
}
addComputed(vm, computed, key) {
/**
* computed 是一个对象
* descriptor 对象里的所有属性
* descriptorFn 对象里的方法
* value 第一次执行完的值
* get 存起来的descriptorFn方法
* dep 依赖的数据 --一个字符数组
*/
// 使用getOwnPropertyDescriptor是为了只在对象中查找属性
// 而不能去原型链上找
const descriptor = Object.getOwnPropertyDescriptor(computed, key),
/**
* 这里是为了预防两种使用computed的方法
* 例如: total(){} / total: ()=> {}
*/
descriptorFn = descriptor.value.get
? descriptor.value.get
: descriptor.value,
// 第一次执行的值
value = descriptorFn.call(vm),
// 将定义的方法保存起来
get = descriptorFn.bind(vm),
// 这个方法依赖的数据
dep = this._collectionDep(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() {
// 设置数据的时候就重新执行
dataItem.value = dataItem.get()
},
})
}
upDate(key, cb) {
this.computedData.map((item) => {
const dep = item.dep
// 修改的监听值是否存在
const _key = dep.find((el) => el == key)
if (_key) {
// 重新执行
item.value = item.get()
// 如果有回调函数就执行
cb && cb(key, item.value)
}
})
}
_addComputedProp(computedProp) {
this.computedData.push(computedProp)
}
// 处理依赖的数据
_collectionDep(fn) {
// 截取this后面的字符
const matched = fn.toString().match(/this\.(.+?)/g)
let result = []
matched.forEach((item) => {
result.push(item.split('.')[1])
})
return result
}
}
Watch
实现的几个必须的方法:
· addWatcher 对数据预处理
· _addWatcherProp 添加数据
· invoke 添加数据(供外部调用)
class Watcher {
constructor() {
this.watchers = []
}
addWatcher(vm, watcher, key) {
this._addWatcherProp({
vm,
key,
//watcher[key] -- 方法
fn: watcher[key].bind(vm),
})
}
invoke(key, newValue, oldValue) {
this.watchers.map((item) => {
if (item.key == key) {
item.fn(newValue, oldValue)
}
})
}
_addWatcherProp(watchProp) {
this.watchers.push(watchProp)
}
}
Vue
vue类的作用是将前面几个类整和起来。
实现的几个功能:
- 数据响应式
- vm挂载data
- 实例化 Wtach Computed 并且使用它们
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)
this.$computed = computedIns.upDate.bind(computedIns)
const watcherIns = this.initeWatcher(vm, watch)
this.$watch = watcherIns.invoke.bind(watcherIns)
}
initComputed(vm, computed) {
const computedIns = new Computed()
for (let key in computed) {
// key -- 方法名
// 给computed增加数据
computedIns.addComputed(vm, computed, key)
}
return computedIns
}
initeWatcher(vm, watch) {
const watcherIns = new Watcher()
// 添加数据
for (let key in watch) {
watcherIns.addWatcher(vm, watch, key)
}
return watcherIns
}
initData(vm) {
this.reavtive(
vm,
(key, value) => {},
(key, newValue, oldValue) => {
this.$computed(key)
this.$watch(key, newValue, oldValue)
}
)
}
// 数据响应式 数据劫持
reavtive(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)
},
})
}
}
}
测试一下
const vm = new Vue({
data() {
return {
a: 1,
b: 2,
}
},
computed: {
total() {
console.log('Computed')
return this.a + this.b
},
},
watch: {
total(newValue, oldValue) {
console.log('total', newValue, oldValue)
},
a(newValue, oldValue) {
console.log('a', newValue, oldValue)
},
b(newValue, oldValue) {
console.log('b', newValue, oldValue)
},
},
})
console.log(vm)
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)
结果: