Open youngwind opened 7 years ago
vue中有一个非常好用的功能:计算属性(computed)
在模板中绑定表达式是非常便利的,但是它们实际上只用于简单的操作。模板是为了描述视图的结构。在模板中放入太多的逻辑会让模板过重且难以维护。这就是为什么 Vue.js 将绑定表达式限制为一个表达式。如果需要多于一个表达式的逻辑,应当使用计算属性。 你可以像绑定普通属性一样在模板中绑定计算属性。Vue 知道 vm.b 依赖于 vm.a,因此当 vm.a 发生改变时,依赖于 vm.b 的绑定也会更新。
来源:https://vuejs.org.cn/guide/computed.html
我们先来具象化一下问题。
// html <div id="app"> <p>姓名:{{user.name}}</p> <p>年龄: {{user.age}}</p> <p>{{info}}</p> </div>
// js const app = new Bue({ el: '#app', data: { user: { name: 'youngwind', age: 24 } }, computed: { info: function () { return `计算出来的属性-> 姓名: ${this.user.name}, 年龄: ${this.user.age}`; } } });
问题是:如何让info跟着name和age动态改变呢?
我们把这个问题拆解成两个更小的问题,然后逐个击破。
ok,我们先来解决第一个问题。(先把第二个问题放一边)
/** * 初始化所有计算属性 * 主要完成一个功能:将计算属性定义的function当成是该属性的getter函数 * @private */ exports._initComputed = function () { // 注意,这里的this指的是bue实例 let computed = this.$options.computed; if (!computed) return; for (let key in computed) { let def = computed[key]; if (typeof def === 'function') { def = { get: def }; def.enumerable = true; def.configurable = true; Object.defineProperty(this.$data, key, def); } } };
关键点说明:
实现效果如下图所示。
从图中我们可以看到,$data里面的info已经有值,并且DOM模板里面的{{info}}也已经正确解析了。 第一个问题解决了,但是,我们同时也看到,当我们改变name和age的时候,info并不会跟着改变!! 下面我们来看看怎么解决这第二个问题。
动态计算难在什么地方? 难在:当name或者age改变的时候,程序如何知道要改变info? 你可能会说,这不明摆着吗,一眼就看出来。 然而,程序不知道啊!我们拆解一下问题。
第2个问题好办,因为我们在《如何实现动态数据绑定》 #87 的时候就已经建立起一套完整的Binding、Watcher和Directive的体系。我们只需要把info指令分别push到wathcer name和wathcer age的_subs里面不就可以了,在这儿就不细说了。 我们重点看第一个问题。 解决思路还是从getter入手:定义info的function被当成了getter,那么当我们访问this.$data.info的时候,就会调用这个function。这个function又会去访问this.user.name和this.user.age,这意味着什么呢? 这意味着会去执行name和age的getter函数啊!所以我们可以自定义name和age的getter函数,让它做一些特殊的事情。 那要做什么事情呢?我们触发(notify)一个get事件,然后这个get事件会传播到$data顶层。我们在$data顶层注册一个colletDep(收集依赖)函数,这样我们不就能知道info依赖于user.name和user.age了吗? 嗯,没错,大概思路就是这样。下面展示部分关键代码,完整的代码可以参考这里
function Watcher(vm, expression, cb, ctx) { this.id = ++uid; this.vm = vm; this.expression = expression; this.cb = cb; this.ctx = ctx || vm; this.deps = Object.create(null); // 这里的getter可以不去细究,其实就是根据expression(比如user.name) // 拼接出它对应的函数,当成getter // 你完全可以理解为调用this.getter()方法其实就是为了得到user.name的值 this.getter = expParser.compileGetter(expression); this.initDeps(expression); }
/** * 要注意,这里的getter.call是完成计算属性的核心, * 因为正是这里的getter.call, 执行了该计算属性的getter方法, * 从而执行该计算属性所依赖的其他属性的get方法 * 从而发出get事件,冒泡到底层, 触发collectDep事件 * @param path {String} 指令表达式对应的路径, 例如: "user.name" */ Watcher.prototype.initDeps = function (path) { this.addDep(path); Observer.emitGet = true; this.vm._activeWatcher = this; // 就是在这儿调用info的getter,进而调用name和age的getter // 进入触发和传播get事件 this.value = this.getter.call(this.vm, this.vm.$data); Observer.emitGet = false; this.vm._activeWatcher = null; };
/** * 收集依赖。 * 为什么需要这个东西呢? * 因为在实现computed计算属性功能的过程中, * 发现程序需要知晓计算出来的属性到底依赖于哪些原先就有的属性 * 这样才能做到在对应原有的属性的_subs数组中添加新属性指令的watcher事件 * @param path {String} get事件传播到顶层时的路径,比如"user.name" * @private */ exports._collectDep = function (event, path) { let watcher = this._activeWatcher; if (watcher) { watcher.addDep(path); } }; // 看,就是在这儿给$data顶层注册收集依赖的事件的 this.observer.on('set', this._updateBindingAt.bind(this)) .on('get', this._collectDep.bind(this));
有两个小细节务必要注意:
最后构造出来的_rootBinding数据结构如下图所示。
具体的实现效果如下图所示,完整的代码参考这里
---EOF---
说得太复杂了,没有讲到关键点,其实计算属性就是defineproperty的一个延伸,如果我自己来写我会这样http://www.codesnippet.cn/detail/0510201615088.html
其实什么watcher,binding都是对defineproperty自然的抽象而已
额, 直接读源码都没那么晕。
前言
vue中有一个非常好用的功能:计算属性(computed)
来源:https://vuejs.org.cn/guide/computed.html
问题
我们先来具象化一下问题。
问题是:如何让info跟着name和age动态改变呢?
我们把这个问题拆解成两个更小的问题,然后逐个击破。
静态计算属性
ok,我们先来解决第一个问题。(先把第二个问题放一边)
关键点说明:
实现效果如下图所示。![bug](https://raw.githubusercontent.com/youngwind/blog/master/image/89/bug.gif)
从图中我们可以看到,$data里面的info已经有值,并且DOM模板里面的{{info}}也已经正确解析了。 第一个问题解决了,但是,我们同时也看到,当我们改变name和age的时候,info并不会跟着改变!! 下面我们来看看怎么解决这第二个问题。
动态计算属性
动态计算难在什么地方? 难在:当name或者age改变的时候,程序如何知道要改变info? 你可能会说,这不明摆着吗,一眼就看出来。 然而,程序不知道啊!我们拆解一下问题。
第2个问题好办,因为我们在《如何实现动态数据绑定》 #87 的时候就已经建立起一套完整的Binding、Watcher和Directive的体系。我们只需要把info指令分别push到wathcer name和wathcer age的_subs里面不就可以了,在这儿就不细说了。 我们重点看第一个问题。 解决思路还是从getter入手:定义info的function被当成了getter,那么当我们访问this.$data.info的时候,就会调用这个function。这个function又会去访问this.user.name和this.user.age,这意味着什么呢? 这意味着会去执行name和age的getter函数啊!所以我们可以自定义name和age的getter函数,让它做一些特殊的事情。 那要做什么事情呢?我们触发(notify)一个get事件,然后这个get事件会传播到$data顶层。我们在$data顶层注册一个colletDep(收集依赖)函数,这样我们不就能知道info依赖于user.name和user.age了吗? 嗯,没错,大概思路就是这样。下面展示部分关键代码,完整的代码可以参考这里
有两个小细节务必要注意:
最后构造出来的_rootBinding数据结构如下图所示。![data](https://raw.githubusercontent.com/youngwind/blog/master/image/89/data.png)
具体的实现效果如下图所示,完整的代码参考这里![demo](https://raw.githubusercontent.com/youngwind/blog/master/image/89/finish.gif)
---EOF---