原本vue的响应式应该才是重中之重。可是网上的文章不少不少。在看computed的实现以前。确定仍是要把vue的响应式如何实现好好看一下。或者说二者根本就是同样的东西。这边推荐几篇文章关于vue的响应式。javascript
vue响应式简单实现html
vue慕课响应式手记vue
仍是看看官网对于响应式的解释:java
总的来讲。vue实现响应式的关键有三个:watcher,dep,observe;app
observe:遍历data中的属性。在get,set方法中设置核心数据劫持异步
dep:每一个属性都有一个本身的dep(消息订阅起)用于订制该属性上的全部观察者函数
watcher:观察者,经过dep实现对响应属性的监听观察。观察获得结果后,主动触发本身的回调oop
能够去看看vue2.3的这三部分源码。中间仍是有不少精美的设计。好比一个全局惟一的Dep.target,在任什么时候候都是惟一的值。以确保同一时间只有一个观察者在订阅。再好比,watcher中也会存下相关的订阅器,实现去重和实现同一个观察者的分组(这里是实现computed的关键),再如。watcher中的id也会惟一。用于异步更新的时候不一样时出发相同的订阅。仔细看看会收获不小。改天我把全部的响应式的代码也整理一下。
在理解了响应式的状况下。咱们来看看computed的实现。最简单的一个demo以下:ui
<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>
咱们来从源码的角度看看发生了什么:this
在初始化实例建立响应式的时候。对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的实现仍是很直观的。组件初始化的时候。computed项和data中的分别创建响应式。data中的数据直接对属性的get,set作数据拦截。而computed则创建一个新的watcher,在组件渲染的时候。先touch一下这个computed的getter函数。将这个watcher订阅起来。这里至关于这个computed的watcher订阅了firstname和lastname。touch完后。Dep.target此时又变为以前那个用于更新组件的。再经过watcher.depend()将这个组统一加上这个订阅。这样一旦firstname和lastname变了。同时会触发两个订阅更新。其中一个即是更新组件。从新re-render的函数。感受看的还不够细啊