vue computed 源码分析
我们来看看computed的实现。最简单的一个demo如下:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <div id="app"> <div name="test">{{computeA}}</div> </div> </body> <script src="vue.js"></script> <script type="text/javascript"> new Vue({ el: '#app', data: function () { return { firstName: 111, lastName: 222 } }, computed: { computeA: function () { return this.firstName + ' ' + this.lastName } }, created(){ setTimeout( () => { this.firstName = 333; },1000 ) } }) </script> </html>
1在初始化实例创建响应式的时候。对options中的computed做了特殊处理:
function initComputed (vm, computed) { var watchers = vm._computedWatchers = Object.create(null); for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; { if (getter === undefined) { warn( ("No getter function has been defined for computed property \"" + key + "\"."), vm ); getter = noop; } } // create internal watcher for the computed property. watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);//为每一个computed项目订制一个watcher // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef); } else { if (key in vm.$data) { warn(("The computed property \"" + key + "\" is already defined in data."), vm); } else if (vm.$options.props && key in vm.$options.props) { warn(("The computed property \"" + key + "\" is already defined as a prop."), vm); } } function defineComputed (target, key, userDef) { if (typeof userDef === 'function') { sharedPropertyDefinition.get = createComputedGetter(key); sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } Object.defineProperty(target, key, sharedPropertyDefinition); } function createComputedGetter (key) {//构造该computed的get函数 return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate();//收集该watcher的订阅 } if (Dep.target) { watcher.depend();//同一为这一组订阅再加上组件re-render的订阅(该订阅负责更新组件) } return watcher.value } } }
组件初始化的时候。computed项和data中的分别建立响应式。data中的数据直接对属性的get,set做数据拦截。而computed则建立一个新的watcher,在组件渲染的时候。先touch一下这个computed的getter函数。将这个watcher订阅起来。这里相当于这个computed的watcher订阅了firstname和lastname。touch完后。Dep.target此时又变为之前那个用于更新组件的。再通过watcher.depend()将这个组统一加上这个订阅。这样一旦firstname和lastname变了。同时会触发两个订阅更新。其中一个便是更新组件。重新re-render的函数。