计算属性是一个很邪门的东西,只要在它的函数里引用了data中的某个属性,当这个属性发生变化的时候,函数仿佛可以嗅探到这个变化,并自动重新执行。
上代码会源源不断的打印出a的值。如果希望b依赖data中的x而变化,只需要保证b函数中有this.x即可。如果函数中没有出现data中的属性,那么无论data中的属性怎么变,b对应的函数一次也不会执行。
Vue是怎么知道计算属性在函数中引用了哪个data属性?这个函数又是怎么知道data属性变了,而且只关心它内部引用的那个属性,别的都不管?官方文档是这么说的:
我们简单模拟实现一个计算属性:a变化时,b自动跟着变化。
var obj = {
a:0,
b:function(){
var a = this.a;
return a + 1;
}
}
由于涉及到Vue的双向绑定的原理,如果你对此不熟,最好先看看《Vue.js双向绑定的实现原理》
程序执行过程:
1、首先b属性会被处理为存取器属性,访问b就会触发其get函数;
2、处理计算属性a时,会执行a的函数,从而会执行this.b,于是触发b的get函数;
3、b的get函数会添加b属性的依赖项,而刚才在处理计算属性过程中,a已经作为依赖项被传给了一个全局变量,b的get函数会检测到这个全局变量,并将其添加到自身的订阅者列表中;
4、对b赋予新的值时,会触发其set函数,set函数中会遍历执行订阅者,a的值就是在这个时候更新的。
再看代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Two-way-data-binding</title> </head> <body> <script> var Dep = null; function defineReactive(obj, key, val) { var deps = []; Object.defineProperty(obj, key, { get: function () { if (Dep) { deps.push(Dep); } return val; }, set: function (newval) { val = newval; deps.forEach(func => func()); } }) } function defineComputed(obj, key, func) { func = func.bind(obj); var value; Dep = function () { value = func(); }; value = func(); Dep = null; Object.defineProperty(obj, key, { get: function () { return value; } }) } var obj = {}; defineReactive(obj, 'a', 0); defineComputed(obj, 'b', function () { var a = this.a; return a + 1; }) </script> </body> </html>
通过对存取器属性、闭包和观察者模式的综合运用,Vue巧妙的实现了计算属性。可以看出,Vue响应式系统的核心理念是“依赖”,DOM节点之所以随数据而变化,是因为节点依赖于数据,计算属性之所以随数据而变化,是因为计算属性依赖于数据,做好响应式的关键就在于处理好依赖关系。
参考文章:https://skyronic.com/blog/vuejs-internals-computed-properties