咱们经过一个简单的 Vue应用 来演示 Vue的响应式属性:html
html: <div id="app"> {{message}} </div> js: let vm = new Vue({ el: '#app', data: { message: '123' } })
在应用中,message 属性即为 响应式属性。 vue
咱们经过 vm.message, vm.$data.message, 可访问 响应式属性 message。node
当咱们经过修改 vm.message(vm.message = '456'), 修改后的数据会 更新到UI界面中。react
vue的官网文档,对响应式属性的原理有一个介绍。设计模式
把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象全部的属性,并使用 Object.defineProperty 把这些属性所有转为 getter/setter。每一个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程当中把属性记录为依赖,以后当依赖项的 setter 被调用时,会通知 watcher 从新计算,从而导致它关联的组件得以更新。数组
官方文档app
以上介绍,只是对响应式原理进行了简单描述,并无深刻细节。所以本文在源码层面,对响应式原理进行梳理,对关键步骤进行解析。dom
响应式原理涉及到的关键步骤以下:ide
Vue.js 给咱们提供了一个 全局构造函数 Vue。 函数
经过 new Vue(options) 生成一个 vue实例,从而能够构建一个 Vue应用。
其中,options 为构造vue实例的配置项,即为 { data, methods, computed, filter ... }
/* options: { data: {...}, methods: {...}, computed: {...}, watch: {...} ... } */ function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } // 根据options, 初始化vue实例 this._init(options) } export default Vue;
vue实例 构造完毕以后,执行实例私有方法 _init(), 开始初始化。
在一个 vue应用 中,存在两种类型的 vue实例 :根vue实例 和 组件vue实例。
根vue实例,由构造函数 Vue 生成。
组件vue实例,由组件构造函数 VueComponent 生成,组件构造函数 继承 自构造函数 Vue。
// 全局方法extend, 会返回一个组件构造函数。 Vue.extend = function(options) { ... // 组件构造函数,用于建立组件 var Sub = function VueComponent(options) { this._init(options); }; // 子类的prototype继承自Vue的prototype // 至关于Sub实例可使用Vue实例的方法 Sub.prototype = Object.create(Vue.prototype); ... return Sub; }
经过一个 根vue实例 和多个 组件vue实例,构成了整个 Vue应用。
在_init方法中,vue实例会执行一系列初始化操做。
在初始化过程当中, 咱们经过全局方法 initState 来初始化vue实例的 data、props、methods、computed、watch 属性。
Vue.prototype._init = function(options) { var vm = this; ... // 其余初始化过程, 包括创建子vue实例和父vue实例的对应关系、给vue实例添加自定义事件、执行beforeCreated回调函数等 // 初始化props属性、data属性、methods属性、computed属性、watch属性 initState(vm); ... // 其余初始化过程,好比执行created回调函数 // vue实例初始化完成之后,挂载vue实例,将模板渲染成html if(vm.$options.el) { vm.$mount(vm.$options.el); } }; function initState (vm: Component) { vm._watchers = []; // new Vue(options) 中的 options const opts = vm.$options; // 将props配置项中属性转化为vue实例的响应式属性 if (opts.props) initProps(vm, opts.props); // 将 methods配置项中的方法添加到 vue实例对象中 if (opts.methods) initMethods(vm, opts.methods); // 将data配置项中的属性转化为vue实例的响应式属性 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } ... }
其中,initData 方法会将 data配置项 中的属性所有转化为 vue实例 的 响应式属性。
initData 方法的主要过程:
function initData(vm) { // 获取data配置项对象 var data = vm.$options.data; // 组件实例的data配置项是一个函数 data = vm._data = typeof data === 'function'? getData(data, vm): data || {}; // 获取data配置项的属性值 var keys = Object.keys(data); // 获取props配置项的属性值 var props = vm.$options.props; // 获取methods配置项的属性值; var methods = vm.$options.methods; var i = keys.length; while(i--) { var key = keys[i]; { // methods配置项和data配置项中的属性不能同名 if(methods && hasOwn(methods, key)) { warn( ("method \"" + key + "\" has already been defined as a data property."), vm ); } } // props配置项和data配置项中的属性不能同名 if(props && hasOwn(props, key)) { "development" !== 'production' && warn( "The data property \"" + key + "\" is already declared as a prop. " + "Use prop default value instead.", vm ); } else if(!isReserved(key)) { // 若是属性不是$,_ 开头(vue的保留属性) // 创建 vue实例 和 _data 的关联关系性 proxy(vm, "_data", key); } } // 观察data对象, 将对象属性所有转化为响应式属性 observe(data, true /* asRootData */); }
全局方法 observe 的做用是用来观察一个对象,将_data对象的属性所有转化为 响应式属性。
// observe(_data, true) function observe(value, asRootData) { if(!isObject(value)) { return } var ob; ... // ob = new Observer(value); ... return ob; } var Observer = function Observer(value) { ... if(Array.isArray(value)) { // 若是value是数组,对数组每个元素执行observe方法 this.observeArray(value); } else { // 若是value是对象, 遍历对象的每个属性, 将属性转化为响应式属性 this.walk(value); } }; // 若是要观察的对象时数组, 遍历数组,而后调用observe方法将对象的属性转化为响应式属性 Observer.prototype.observeArray = function observeArray(items) { for(var i = 0, l = items.length; i < l; i++) { observe(items[i]); } }; // 遍历obj的属性,将obj对象的属性转化为响应式属性 Observer.prototype.walk = function walk(obj) { var keys = Object.keys(obj); for(var i = 0; i < keys.length; i++) { // 给obj的每个属性都赋予getter/setter方法。 // 这样一旦属性被访问或者更新,这样咱们就能够追踪到这些变化 defineReactive(obj, keys[i], obj[keys[i]]); } };
经过 defineProperty 方法, 提供属性的 getter/setter 方法。
读取 属性时,触发 getter,将与响应式属性相关的vue实例保存起来。
修改 属性时,触发 setter,更新与响应式属性相关的vue实例。
function defineReactive(obj, key, val, customSetter, shallow) { // 每个响应式属性都会有一个 Dep对象实例, 该对象实例会存储订阅它的Watcher对象实例 var dep = new Dep(); // 获取对象属性key的描述对象 var property = Object.getOwnPropertyDescriptor(obj, key); // 若是属性是不可配置的,则直接返回 if(property && property.configurable === false) { return } // 属性原来的getter/setter var getter = property && property.get; var setter = property && property.set; // 若是属性值是一个对象,递归观察属性值, var childOb = !shallow && observe(val); // 从新定义对象obj的属性key Object.defineProperty(obj, key, { enumerable : true, configurable : true, get : function reactiveGetter() { // 当obj的某个属性被访问的时候,就会调用getter方法。 var value = getter ? getter.call(obj) : val; // 当Dep.target不为空时,调用dep.depend 和 childOb.dep.depend方法作依赖收集 if(Dep.target) { // 经过dep对象, 收集依赖关系 dep.depend(); if(childOb) { childOb.dep.depend(); } // 若是访问的是一个数组, 则会遍历这个数组, 收集数组元素的依赖 if(Array.isArray(value)) { dependArray(value); } } return value }, set : function reactiveSetter(newVal) { // 当改变obj的属性是,就会调用setter方法。这是就会调用dep.notify方法进行通知 var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if(newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if("development" !== 'production' && customSetter) { customSetter(); } if(setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); // 当响应式属性发生修改时,经过dep对象通知依赖的vue实例进行更新 dep.notify(); } }); }
响应式属性, 经过一个 dep 对象, 收集依赖响应式属性的vue实例,在属性改变时 通知vue实例更新。
一个 响应式属性, 对应一个 dep 对象。
在观察者设计模式中,有两种角色:Subject 和 Observer。
Subject 会维护一个 Observer的依赖列表。当 Subject 发生变化时,会通知 Observer 更新。
在vue中,响应式属性做为Subject, vue实例做为Observer, 响应式属性的更新会通知vue实例更新。
响应式属性经过 dep 对象来收集 依赖关系 。一个响应式属性,对应一个dep对象。
var Dep = function Dep() { // dep对象的id this.id = uid++; // 数组,用来存储依赖响应式属性的Observer this.subs = []; }; // 将Observer添加到dep对象的依赖列表中 Dep.prototype.addSub = function addSub(sub) { // Dep对象实例添加订阅它的Watcher this.subs.push(sub); }; // 将Observer从dep对象的依赖列表中删除 Dep.prototype.removeSub = function removeSub(sub) { // Dep对象实例移除订阅它的Watcher remove(this.subs, sub); }; // 收集依赖关系 Dep.prototype.depend = function depend() { // 把当前Dep对象实例添加到当前正在计算的Watcher的依赖中 if(Dep.target) { Dep.target.addDep(this); } }; // 通知Observer更新 Dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice(); // 遍历全部的订阅Watcher,而后调用他们的update方法 for(var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } };
经过 defineProperty 方法, 给vue实例对象添加属性,提供属性的 getter/setter 方法。
读取vue实例的属性( data配置项中的同名属性 ), 触发 getter,读取 _data 的同名属性。
修改vue实例的属性( data配置项中的同名属性 ), 触发 setter,修改 _data 的同名属性。
// proxy(vm, _data, 'message') function proxy(target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter() { return this[sourceKey][key] }; sharedPropertyDefinition.set = function proxySetter(val) { this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); }
经过 proxy 方法,vue实例 可代理私有属性 _data, 即经过 vue实例 能够访问/修改 响应式属性。
结合源码理解, 响应式属性 的原理为: