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

不懂没关系,请往下看~

一、initData

见名知意,这里是获取你输入数据开始的地方。通过 vm.$options.data(vm是viewModel,也就是连接view和model(js)的vue桥梁)获取到你定义的data,但是这里在真正对你的数据进行处理之前需要进行异常处理例如:

1)常见的将data写为data:{name:'zhangkankan'}的形式(考虑到组件复用问题,所以选择的是return对象方式不断开辟存储空间存储同一component的不同数据,防止组件之间相互影响)

2)遍历每一个data中你传入的key防止key重复。

3)比较你的data中是否有和props重名的key。

等等.......

通过以上的异常检查,才是认可了你的这个data(){return{内容}}形式。于是现在开始拿到return的内容对象对其经行处理。进入到observe(data, true /* asRootData */)

二、Observe()

现在将刚才的data对象传入,请注意这里:

  if (!isObject(value) || value instanceof VNode) {return}

在这里我们采用逆向思维哈:1)刚传入的data已经是对象了为什么还要判断?2)vnode和这里没关系为什么还要判断?

第一个问题说明这段代码(即observe函数)是被重复使用的,也就是说会有不是对象的数据传来(后面会讲),同时要知道return的data中的array也算是obj类型(后面对于这个return的类型会产生两种不同做法,后面有讲);第二个问题,vnode是指一个虚拟的node节点,通俗讲就是js根据类制造出来的一个node实例,里面有很多可以描述dom的属性。node组成dom,vnode组成vdom。通过将每一个状态变为每一个不是真正dom但却包含dom内容可为树状的实例结构,于是便可以用js实例的方式不断new出一整个dom树,也就是

虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼 —— vue 官网。

某个vnode如下表示:

{sel: 'div',// 表述标签,如p、sapndata: {},children: undefined,text: 'virtual dom', //标签内的文本,ele: undefined,key: undefined
}

这样的做法便于,如果存在数据更改需要重新渲染视图的时候,不是将之前新和旧dom整个树对比(全部查找一遍,找到需要修改的部分),而是只改这个node节点的实例所对应部分即可。(从侧面说明,对每一个vnode的改变是有observe的,一旦vnode里面属性变化,就重新绘制,并且只改这个部分,这也是为什么这里要排除当前对象为VNode的实例,因为当前是对data进行监控而不是vnode)。

接着主线向下

再排除是其他类型且不是vnode之后,会进行判断。

if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__
//如果你这个data对象有_ob_属性并且这个属性也是一个实例?
//因为new observer后会对每一个对象上加一个_ob_,并且def(value, '__ob__', this),
//即将value的值赋给_ob_,这样做的目的是防止同一个data对象杯多次new观测,说明这个data已经被注册过了,于是拿出之前的那个data属性对这个ob赋值(实际上就是同一个),等于没有操作} else if (//巴拉巴拉一堆判断) {ob = new Observer(value)//对这个对象创建监测}
if (asRootData && ob) {ob.vmCount++  // number of vms that have this object as root $data 就是用了几次}

三、Observer类(重点)

排除了一系列干扰,现在终于是拿到了data对象对他进行处理了。在这个类中,首先我们会用value保存data的值,同时对data对象开启一个Dep,准备对凡是用到data数据的依赖进行收集。这里开始有分支,如果当时return的是Array类型怎么处理,如果是Object类型怎么处理,两种数据结构构造不同,处理不同(主要是对依赖收集方式不同产出两条分支)。

   if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}this.observeArray(value)} else {this.walk(value)}

四、return的为Object类型进入walk()-->defineReactive()(重点

  walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])}}

代码不多,基本意思很明确拿到每一个key和value传入defineReactive();下面进入该函数

技巧一:数据劫持

Object.defineProperty的介绍,我们不难发现,当我们访问或设置对象的属性的时候,都会触发相对应的函数,然后在这个函数里返回或设置属性的值

  既然如此,我们当然可以在触发函数的时候动一些手脚做点我们自己想做的事情,这也就是“劫持”操作

  在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并“种下”一个监听器。

技巧二:发布订阅者模式

  对于每一个key都有一个对应的dep(订阅对象),通过数据劫持可以收集依赖(即每一个watcher实例),所以一般这个为一个set结构存取。之后如果set函数被触发就会通知dep,dep会调用函数对内存储的watchers进行处理。

export function defineReactive (obj: Object,key: string,val: any,customSetter?: Function
) {const dep = new Dep()//创建订阅对象const property = Object.getOwnPropertyDescriptor(obj, key)//获取obj对象的key属性的描述//属性的描述特性里面如果configurable为false则属性的任何修改将无效if (property && property.configurable === false) {return}// cater for pre-defined getter/settersconst getter = property && property.getconst setter = property && property.setlet childOb = observe(val)//创建一个观察者对象Object.defineProperty(obj, key, {enumerable: true,//可枚举configurable: true,//可修改get: function reactiveGetter () {const value = getter ? getter.call(obj) : val//先调用默认的get方法取值//这里就劫持了get方法,也是作者一个巧妙设计,在创建watcher实例的时候,通过调用对象的get方法往订阅器dep上添加这个创建的watcher实例
//这里是更复杂的多情况判断,比如是数组、或者包有其他类型if (Dep.target) {dep.depend()if (childOb) {childOb.dep.depend()}if (Array.isArray(value)) {dependArray(value)}}return value//返回属性值},set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : val//先取旧值if (newVal === value) {return}//这个是用来判断生产环境的,可以无视if (process.env.NODE_ENV !== 'production' && customSetter) {customSetter()}if (setter) {setter.call(obj, newVal)} else {val = newVal}childOb = observe(newVal)//继续监听新的属性值dep.notify()//这个是真正劫持的目的,要对订阅者发通知了,通知里面存储的对应watcher需要有变动了}})
}

那么以上便是完成了对于一个data方法return一个object类型内所有属性的监听。从宏观角度来说数据结构模式为weakmap(obj1,dep:map;obj2,dep:map),一个类对应一个map,一个map由key和对应dep组成。

回到之前的另一种类型,如果return的是array类型。

五、return的为Array类型(重点

 有一说一我觉得上面讲的那个对于每一个属性的依赖收集方式设计和下面这个对于arr中每一个元素的设计模式真的是响应式最亮眼的地方。

  下面是new Observe类里面的判断

  if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}this.observeArray(value)} 

首先:我为什么监听数组?因为数组改变了我数据也应该通知视图重新画画。所以我需要做的是对每一个可以改变数组内容的方法中加入我的其他操作。

1)protoAugment(value, arrayMethods):这个方法其实就是上面一句话的体现,也是对return类型arr操作中很关键的一步,即对传入对象的__proto__重写。

__proto__(隐式原型)是每个对象都有的属性,但是prototype(显示原型)是函数才有的原型(所以说函数作为一个由function Function(){}new出来的对象,有两原型,__proto__指向Function的prototype,prototype指向构造器对象)

它不是一个规范属性,该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它。大多数情况下,__proto__可以理解为“构造器的原型”,即__proto__===constructor.prototype。(sorry扯远了)回归正题---------------------------

其中传入的arrayMethods便是可以改变arr内容的所有函数方法,现在对其进行重写

onst arrayProto = Array.prototype//通过原型拿到对象
export const arrayMethods = Object.create(arrayProto)
//创建一个新的对象,使用现有的对象来提供新建对象的__proto__,
// 实际上就是改变了array的prototype,方法都写在现式原型上
//之后到处使用这个__proto__const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'
]/*** Intercept mutating methods and emit events*/
methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method]//去除每一个原有方法//重定义def(arrayMethods, method, function mutator (...args) {//说明用了之前的arr定义的原来的方法来处理数据const result = original.apply(this, args)const ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}if (inserted) ob.observeArray(inserted)// notify changeob.dep.notify()//添加的新功能在这里return result})
})

2)ob.observeArray方法

//这里是new Observe中对于return是arr类型 的处理
if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}this.observeArray(value)}  //下面是重写arr的代码片段
switch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}if (inserted) ob.observeArray(inserted)

细细观察可发现,不仅在上面这个本修改arr原型方法的函数中调用了这个函数,并且也在判断是array类型后用了这个函数。他的目的在于,push\unshift\splice操作会导致有新的数据进入;并且arr里面本身自己可能就有些元素是obj类型,这也就说明改变arr内容不止是arr的数组操作了,也包含了obj的操作,所以需要对这一个obj的属性进行之前的监听。这也是之前提过的,为什么要复用obseve()函数,因为这个函数是对于obj类型监听的一系列操作。所以我们会再次提到这个

 if (!isObject(value) || value instanceof VNode) {return}

如果传入的是obj类型,那么对arr中的obj元素每一个key再开启dep,重复之前的操作,否则退出。

现在请回头看看我的那张最顶部的图。

关于如果触发dep开始对每一个watcher的调用方法ob.dep.notify()下篇文章再说~

Vue响应式原理(看这一篇就够了)相关推荐

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

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

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

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

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

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

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

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

  5. Vue学习 — Vue响应式原理

    一. Object.defineProperty 在学习vue响应式原理之前,必须搞懂 Object.defineProperty. Object.defineProperty(obj, prop, ...

  6. 详解Vue响应式原理

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

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

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

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

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

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

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

最新文章

  1. CSS学习——基础分类整理
  2. 转:集群和分布式的区别
  3. 55 MM配置-评估和科目设置-定义账户分类参考
  4. jpa基于按annotation的hibernate主键生成策略
  5. windows分辨率修改工具_小视频压缩、倒放,这些小工具轻松搞定
  6. DXUT框架剖析(7)
  7. 【树状数组 思维题】luoguP3616 富金森林公园
  8. 模拟调制与抗噪声性能MATLAB,毕业论文 模拟通信系统抗噪声性能分析
  9. 蓝桥杯省赛2020 成绩统计
  10. js监听只读文本框_javascript 监听文本框输入
  11. 一步一步编写12306抢票软件
  12. 360与腾讯之争之厚黑学分析
  13. android studio代码格式化设置,Android studio kotlin代码格式化操作
  14. MA Chapter 3 Presenting information(SRCharlotte)
  15. Kaggle入门项目,泰坦尼克号幸存者
  16. 怎么说话比说什么更重要
  17. Mac 安装 nvm
  18. 第九届“图灵杯”NEUQ-ACM部分汇总
  19. 水平垂直居中的几种方式
  20. 阿里云备案成功的域名可以用腾讯云的服务器吗?

热门文章

  1. “烫烫烫烫烫烫烫烫烫烫烫烫烫...
  2. 苹果ppt_毫无惊喜的苹果新品发布会,用到的这4个PPT技巧却值得学习
  3. vscode在html看到图片的插件_利用花瓣插件 下载高清大图
  4. 2015计算机考研重点,2015考研管理综合真题及答案:逻辑推理一(网友版)
  5. SDSoC软硬件协同设计流程系列——1.基于SDSoC的软硬件协同设计流程简介
  6. 服务器端的相对地址与绝对地址
  7. LSTM论文翻译-《Understanding LSTM Networks》
  8. lavarel5.2中多表联查 搜索后分页
  9. 英语影视台词---绿皮书(1)
  10. mj评[杜拉拉升职记]-8.5分