通过模拟一个简单的例子来理解 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