描述

 我们通过一个简单的 Vue应用 来演示 Vue的响应式属性

html:<div id="app">{{message}}</div>js:let vm = new Vue({el: '#app',data: {message: '123'}})

 在应用中,message 属性即为 响应式属性

 我们通过 vm.message, vm.$data.message, 可访问 响应式属性 message

 当我们通过修改 vm.message(vm.message = '456'), 修改后的数据会 更新到UI界面中

问题

  • 为什么修改 vm.message, 即可触发 UI更新
  • vm.messagedata.message 的关联关系;

官方介绍

  vue的官网文档,对响应式属性的原理有一个介绍。

把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

 官方文档

  以上介绍,只是对响应式原理进行了简单描述,并没有深入细节。因此本文在源码层面,对响应式原理进行梳理,对关键步骤进行解析。

  响应式原理涉及到的关键步骤如下:

  • 构建vue实例
  • vue实例data属性初始化,构建响应式属性
  • 将vue实例对应的template编译为render函数
  • 构建vue实例的watcher对象
  • 执行render函数,构建VNode节点树,同时建立响应式属性和watcher对象的依赖关系
  • 将VNode节点渲染为dom节点树
  • 修改响应式属性,触发watcher的更新,重新执行render函数,生成新的VNode节点树
  • 对比新旧Vnode,重新渲染dom节点树

构造函数 - Vue

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应用

Vue.prototype._init

  在_init方法中,vue实例会执行一系列初始化操作。

  在初始化过程中, 我们通过全局方法 initState 来初始化vue实例的 datapropsmethodscomputedwatch 属性。

     Vue.prototype._init = function(options) {var vm = this;...  // 其他初始化过程, 包括建立子vue实例和父vue实例的对应关系、给vue实例添加自定义事件、执行beforeCreated回调函数等// 初始化props属性、data属性、methods属性、computed属性、watch属性initState(vm);... // 其他初始化过程,比如执行created回调函数// vue实例初始化完成以后,挂载vue实例,将模板渲染成htmlif(vm.$options.el) {vm.$mount(vm.$options.el);}};function initState (vm: Component) {vm._watchers = [];// new Vue(options) 中的 optionsconst 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

initData 方法的主要过程:

  • 根据data配置项,创建vue实例的私有属性: _data
  • 通过 observe 方法,将 _data 对象中的属性转化为 响应式属性
  • 通过全局方法proxy, 建立 vue实例_data 的关联关系。
    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

  全局方法 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]]);}};

defineReactive

  通过 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/settervar getter = property && property.get;var setter = property && property.set;// 如果属性值是一个对象,递归观察属性值,var childOb = !shallow && observe(val);// 重新定义对象obj的属性keyObject.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 对象。

Dep

  在观察者设计模式中,有两种角色:SubjectObserver

Subject 会维护一个 Observer的依赖列表。当 Subject 发生变化时,会通知 Observer 更新。

  在vue中,响应式属性作为Subject, vue实例作为Observer, 响应式属性的更新会通知vue实例更新。

  响应式属性通过 dep 对象来收集 依赖关系 。一个响应式属性,对应一个dep对象。

    var Dep = function Dep() {// dep对象的idthis.id = uid++;// 数组,用来存储依赖响应式属性的Observerthis.subs = [];};// 将Observer添加到dep对象的依赖列表中Dep.prototype.addSub = function addSub(sub) {// Dep对象实例添加订阅它的Watcherthis.subs.push(sub);};// 将Observer从dep对象的依赖列表中删除Dep.prototype.removeSub = function removeSub(sub) {// Dep对象实例移除订阅它的Watcherremove(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 firstvar subs = this.subs.slice();// 遍历所有的订阅Watcher,然后调用他们的update方法for(var i = 0, l = subs.length; i < l; i++) {subs[i].update();}};

proxy

  通过 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实例 可以访问/修改 响应式属性

总结

  结合源码理解, 响应式属性 的原理为:

vue - 响应式原理梳理(一)相关推荐

  1. Vue响应式原理的简单模型

    1.前言 最近在梳理vue响应式的原理,有一些心得,值得写一篇博客出来看看. 其实之前也尝试过了解vue响应式的原理,毕竟现在面试看你用的是vue的话,基本上都会问你几句vue响应式的原理.以往学习这 ...

  2. 深入了解 Vue 响应式原理(数据拦截)

    前言 在上一章节我们已经粗略的分析了整个的Vue 的源码(还在草稿箱,需要梳理清楚才放出来),但是还有很多东西没有深入的去进行分析,我会通过如下几个重要点,进行进一步深入分析. 深入了解 Vue 响应 ...

  3. vue 数组删除 dome没更新_详解Vue响应式原理

    摘要: 搞懂Vue响应式原理! 作者:浪里行舟 原文:深入浅出Vue响应式原理 Fundebug经授权转载,版权归原作者所有. 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是 ...

  4. Vue 响应式原理(双向数据绑定) 怎样实现 响应式原理?

    Vue 响应式原理(双向数据绑定) 怎样实现 响应式原理? 我们在Vue里面,定义在Data里的属性,叫做响应式属性. 每一个vue组件被创建的时候,同时还有一个对象被创建出来了,这个对象我们是看不到 ...

  5. 手把手教你剖析vue响应式原理,监听数据不再迷茫

    Object.defineProperty实现vue响应式原理 一.组件化基础 1."很久以前"的组件化 (1)asp jsp php 时代 (2)nodejs 2.数据驱动视图( ...

  6. Vue响应式原理(看这一篇就够了)

    你肯定听说过Object.denfineProperty或是Proxy\reflect,这的确是在VUE响应式原理中起重要作用的一部分代码,但这远远不能代表整个流程的精妙.上图: 不懂没关系,请往下看 ...

  7. Vue源码--解读vue响应式原理

    原文链接:https://geniuspeng.github.io/2018/01/05/vue-reactivity/ Vue的官方说明里有深入响应式原理这一节.在此官方也提到过: 当你把一个普通的 ...

  8. Vue响应式原理详细讲解

    面试官:请你简单的说说什么是Vue的响应式. 小明:mvvm就是视图模型模型视图,只有数据改变视图就会同时更新. 面试官:说的很好,回去等通知吧. 小明:.... Vue响应式原理 先看官方的说法 简 ...

  9. 一篇文章带你吃透VUE响应式原理

    本篇响应式原理介绍比较长,全文大概1w+字.虽然内容繁杂,但阅读过后,绝对会让你对vue的响应式有更加深刻的理解. 分块阅读,效果更佳.(建议读者有一定vue使用经验和基础再食用) 首先上图,下面这张 ...

最新文章

  1. java.io.file jar_IDEA Maven 打包运行 jar java.io.FileNotFoundException: 问题?
  2. c++类与类的关联(Association)关系
  3. c++中的全排列函数next_permutation()
  4. 隐藏的图片在浏览器中的请求
  5. vue如何写原生js_纯技巧向:React, Vue, Rxjs 和原生 JS 代码大乱斗
  6. linux如何让普通用户有root权限
  7. HDU3790 最短路径问题【Dijkstra算法】
  8. 雀巢联手京东,发布人工智能家庭营养健康助手
  9. mysql主从怎么确保数据一致_如何保证主从复制数据一致性
  10. 苹果屏蔽更新描述文件_安装iOS屏蔽更新描述文件教程方法
  11. 来个水帖:下载Dev
  12. centos Unison+Inotify双向同步
  13. 干货 | 4步带你完成私有云盘搭建
  14. 使用LocalStorage存储用户已填写的表单信息(意外刷新后自动填充)
  15. 中国移动通信互联网短信网关接口协议 (China Mobile Peer to Peer, CMPP) (V2.0)
  16. jquery获取元素索引值index()
  17. adb shell dumpsys appops
  18. BrightlyPro - 照片视屏后期自动调色增亮工具
  19. 自动批量处理人像照片
  20. 带宽、速率(波特率、比特率)和码元宽度简述

热门文章

  1. java实现条件编译
  2. Linq 多表连接查询join
  3. (十)装饰器模式详解(与IO不解的情缘)
  4. Spring AOP中定义切点(PointCut)和通知(Advice)
  5. mysql dba系统学习(12)mysql的数据文件 mysql dba系统学习(13)mysql的体系结构
  6. 从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式
  7. 中科大刘和刚老师的计算机图形学资料 for students
  8. 降维(一)----说说主成分分析(PCA)的源头
  9. 《分布式操作系统》知识点(22~28)四
  10. 【python图像处理】python绘制饼状图