通过模拟一个简单的例子来理解 Vue.js 的计算属性
通过 Object.defineProperty 的 getter setter 来实现对象的数据劫持,例如:
const person = {}
Object.defineProperty(person, 'name', {
get: function(){
console.log('getting name')
return 'Tink'
}
})
console.log('this person is ', person.name)
// [console]: getting name
// this person is Tink
Vue.js 最基本的架构,就是可以把一个普通对象转成可被观察(observable)的对象。
对上面的 person 对象实现一个简单的 Observer 如下:
function defineReactive(obj, key, val){
Object.defineProperty(obj, key, {
get: function(){
return val
},
set: function(newVal){
val = newVal
}
})
}
const person = {}
defineReactive(person, 'name', 'tink')
defineReactive(person, 'age', 20)
if(person.age >= 18){
console.log('man')
}else {
console.log('boy')
}
person.age = 10
首先创建一个名为 defineComputed 的函数来定义计算属性
defineComputed 函数应该可以这样用
defineComputed(
person, // 计算属性所属的对象
'status', // 需要做计算的属性
function(){ // 执行计算的具体方法
console.log('status getter')
if(person.age > 18){
return 'man'
}else {
return 'boy'
}
},
function(newVal){
// 计算属性的值更新后执行
console.log('status is now: ' + newVal)
}
)
// 还可以像普通属性一样访问计算属性
console.log('The person is a ' + person.status)
然后,具体的实现一个简单的 defineComputed 函数
function defineComputed(obj, key, computedFn, updateCallBack){
Object.defineProperty(obj, key, {
get: function(){
// 调用 具体的计算函数 并返回计算后的值
return computedFn()
},
set: function(){
// nothing 不做 setter
}
})
}
现在有两个问题
computedFn updateCallBack ) 最后,计算属性的预期应是如下的方式
person.age = 20 // console: status == 'man' person.age = 10 // console: status == 'boy'
首先,添加一个全局的 Dep 对象
const Dep = {
target: null
}
这就是用来跟踪的 Dependency tracker
然后,给 defineComputed 函数添加依赖
function defineComputed(obj, key, computedFn, updateCallBack){
const onDependencyUpdate = () => {
}
Object.defineProperty(obj, key, {
get: function(){
Dep.target = onDependencyUpdate
const newVal = computedFn()
// reset target 其他属性就不会再添加这个依赖
Dep.target = null
return newVal
},
set: function(){
// nothing 不做 setter
}
})
}
我们再来改进最初实现 Observer 的 defineReactive 函数
function defineReactive(obj, key, val){
const deps = []
Object.defineProperty(obj, key, {
get: function(){
if(Dep.target && deps.indexOf(Dep.target) == -1){
deps.push(Dep.target)
}
return val
},
set: function(newVal){
val = newVal
// 通知所有 依赖器 执行计算函数: onDependencyUpdate
deps.forEach( updateCallBack => {
updateCallBack()
})
}
})
}
同时我们改进下 defineComputed 方法中的 onDependencyUpdate 来执行回调
const onDependencyUpdate = () => {
const val = computedFn()
updateCallBack(val)
}
将 person.status 设置为计算属性
person.age = 20
defineComputed({
person,
'stauts',
function(){
if(person.age > 18){
console.log('this is man')
return 'man'
}else {
console.log('this is boy')
return 'boy'
}
},
function(newVal){
console.log('person.status now is ' + newVal)
}
})
console.log('person.status is ' + person.status)
person.status 的 get() 被调用,并把 Dep.target 设为它的回调
执行 person.status 的 get() 里的计算函数
person.age 的 get() 检查 Dep 是否有可用的 target
计算函数获取到新的值并返回。而且现在 person.age 的值只要变化就会通知给 person.status