VIP用户交流群:462197261 收藏本站北冥有鱼 互联网前沿资源第一站 助力全行业互联网+
在线客服:78895949
tonglan
  • 当前位置:
  • Vue响应式原理Observer、Dep、Watcher理解

    开篇

    最近在学习Vue的源码,看了网上一些大神的博客,看起来感觉还是蛮吃力的。自己记录一下学习的理解,希望能够达到简单易懂,不看源码也能理解的效果😆

    Object.defineProperty

    相信很多同学或多或少都了解Vue的响应式原理是通过Object.defineProperty实现的。被Object.defineProperty绑定过的对象,会变成「响应式」化。也就是改变这个对象的时候会触发get和set事件。进而触发一些视图更新。举个栗子🌰

    function defineReactive (obj, key, val) {
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
          console.log('我被读了,我要不要做点什么好?');
          return val;
        },
        set: newVal => {
          if (val === newVal) {
            return;
          }
          val = newVal;
          console.log("数据被改变了,我要把新的值渲染到页面上去!");
        }
      })
    }
    
    let data = {
      text: 'hello world',
    };
    
    // 对data上的text属性进行绑定
    defineReactive(data, 'text', data.text);
    
    console.log(data.text); // 控制台输出 <我被读了,我要不要做点什么好?>
    data.text = 'hello Vue'; // 控制台输出 <hello Vue && 数据被改变了,我要把新的值渲染到页面上去!>
    
    

    Observer 「响应式」

    Vue中用Observer类来管理上述响应式化Object.defineProperty的过程。我们可以用如下代码来描述,将this.data也就是我们在Vue代码中定义的data属性全部进行「响应式」绑定。

    class Observer {
      constructor() {
        // 响应式绑定数据通过方法
       observe(this.data);
      }
    }
    
    export function observe (data) {
      const keys = Object.keys(data);
      for (let i = 0; i < keys.length; i++) {
        // 将data中我们定义的每个属性进行响应式绑定
        defineReactive(obj, keys[i]);
      }
    }
    
    

    Dep 「依赖管理」

    什么是依赖?

    相信没有看过源码或者刚接触Dep这个词的同学都会比较懵。那Dep究竟是用来做什么的呢? 我们通过defineReactive方法将data中的数据进行响应式后,虽然可以监听到数据的变化了,那我们怎么处理通知视图就更新呢?

    Dep就是帮我们收集【究竟要通知到哪里的】。比如下面的代码案例,我们发现,虽然data中有text和message属性,但是只有message被渲染到页面上,至于text无论怎么变化都影响不到视图的展示,因此我们仅仅对message进行收集即可,可以避免一些无用的工作。

    那这个时候message的Dep就收集到了一个依赖,这个依赖就是用来管理data中message变化的。

    <div>
      <p>{{message}}</p>
    </div>
    
    data: {
      text: 'hello world',
      message: 'hello vue',
    }
    

    当使用watch属性时,也就是开发者自定义的监听某个data中属性的变化。比如监听message的变化,message变化时我们就要通知到watch这个钩子,让它去执行回调函数。

    这个时候message的Dep就收集到了两个依赖,第二个依赖就是用来管理watch中message变化的。

    watch: {
      message: function (val, oldVal) {
        console.log('new: %s, old: %s', val, oldVal)
      },
    }    
    

    当开发者自定义computed计算属性时,如下messageT属性,是依赖message的变化的。因此message变化时我们也要通知到computed,让它去执行回调函数。 这个时候message的Dep就收集到了三个依赖,这个依赖就是用来管理computed中message变化的。

    computed: {
      messageT() {
        return this.message + '!';
      }
    }
    

    图示如下:一个属性可能有多个依赖,每个响应式数据都有一个Dep来管理它的依赖。

    如何收集依赖

    我们如何知道data中的某个属性被使用了,答案就是Object.defineProperty,因为读取某个属性就会触发get方法。可以将代码进行如下改造:

    function defineReactive (obj, key, val) {
      let Dep; // 依赖
    
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
          console.log('我被读了,我要不要做点什么好?');
          // 被读取了,将这个依赖收集起来
          Dep.depend(); // 本次新增
          return val;
        },
        set: newVal => {
          if (val === newVal) {
            return;
          }
          val = newVal;
          // 被改变了,通知依赖去更新
          Dep.notify(); // 本次新增
          console.log("数据被改变了,我要把新的值渲染到页面上去!");
        }
      })
    }
    
    

    什么是依赖

    那所谓的依赖究竟是什么呢?上面的图中已经暴露了答案,就是Watcher。

    Watcher 「中介」

    Watcher就是类似中介的角色,比如message就有三个中介,当message变化,就通知这三个中介,他们就去执行各自需要做的变化。

    Watcher能够控制自己属于哪个,是data中的属性的还是watch,或者是computed,Watcher自己有统一的更新入口,只要你通知它,就会执行对应的更新方法。

    因此我们可以推测出,Watcher必须要有的2个方法。一个就是通知变化,另一个就是被收集起来到Dep中去。

    class Watcher {
      addDep() {
        // 我这个Watcher要被塞到Dep里去了~~
      },
      update() {
        // Dep通知我更新呢~~
      }, 
    }
    

    总结

    回顾一下,Vue响应式原理的核心就是Observer、Dep、Watcher。

    Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher。

    在数据被改的时候,触发set方法,通过对应的所有依赖(Watcher),去执行更新。比如watch和computed就执行开发者自定义的回调方法。

    本篇文章属于入门篇,能够先简单的理解Observer、Dep、Watcher三者的作用和关系。后面会逐渐详细和深入,循序渐进的理解和学习。

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持北冥有鱼。

    您可能感兴趣的文章:

    • 浅谈Vue的响应式原理
    • vue.js响应式原理解析与实现
    • Vue实现双向绑定的原理以及响应式数据的方法
    • 浅谈Vue 数据响应式原理
    • 代码详解Vuejs响应式原理
    • 通过图带你深入了解vue的响应式原理

    广而告之:
    热门推荐:
    利用JS测试目标网站的打开响应速度

    闲来无事,用JS写了一个简单的测试目录网站打开速度的小东西,注意这个只是本机打开目录网站的速度,不代表其它用户的打开也是这个速度,实际上也可用于测试本地网络速度 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD···

    实例讲解如何在PHP的Yii框架中进行错误和异常处理

    Yii已经默认已经在CApplication上实现了异常和错误的接管,这是通过php的set_exception_handler,set_error_handler实现的。通过这两个PHP内置函数,可以对程序中未捕获的异常以及错误进行接管处理,从而提高程序的可维护性。这在大型系统是至关重要的,当发生错误时,我们希望···

    PHP中mysql

    本文实例讲述了PHP中mysql_field_type()函数用法。分享给大家供大家参考。具体如下: 定义和用法:mysql_field_type() 函数返回结果集中指定字段的类型,如果成功,则返回指定字段的类型,如果失败,则返回 false. 语法:mysql_field_type(data,field_offset) 参数 描述 data 必···

    常用PHP封装分页工具类

    分页基本上是每个项目都会使用到的,所以呢,把它封装成一个工具类,以后直接调用就可以了(虽然TP框架的灰常强大,但是自己封一个也未尝不可。),这样既省时又省力还赚‘工分'。 我封的这个分页工具类还比较完整,有首页,上一页,下一页,末页和可选数量的页码数量(也就是当···

    Json

    从APP端或从其他页面post,get过来的数据一般因为数组形式。因为数组形式不易传输,所以一般都会转json后再发送。本以为发送方json_encode(),接收方json_decode(),就解决的问题,结果发现,json_decode()后是NULL。 一般会反应是少了一个参数“true”,但是回去看就是 json_dec···

    PHP抓取、分析国内视频网站的视频信息工具类

    使用方法:复制代码 代码如下:require_once "VideoUrlParser.class.php";$url = "http://v.youku.com/v_show/id_XMjkwMzc0Njg4.html";$info = VedioUrlParser::parse($url);echo $info;说明:调用该工具php文件VideoUrlParser.class.php,$url变量后面的字符串为视频页的···

    dedecms顶级列表做首页,实现二级下拉菜单

    dedecms顶级列表做首页,实现二级下拉菜单  顶级列表如果不做首页的话 很容易实现二级下拉,这里做了首页,再做下拉 就等于做了三级下拉,dedecms默认模版里不支持三级下拉,让我很悲剧。 想了2天怎么给网站去改版,终于搞定了 代码记录下,修改了原模版中的文件···

    mysql解压包的安装基础教程

    由于换了新电脑,所以的环境都要到新电脑去配置。突然发现mysql的配置忘了,然后百度又重新来一遍。 特地写一篇文章记录一下,让自己长长记性。 1.安装包下载 2.配置my.ini文件 配置信息如下: [mysql] # 设置mysql客户端默认字符集 default-character-set=utf8 [mysql···

    CSS实现梯形标签页的方法

    在web设计中,梯形标签页是很常见的一种形式,但是梯形又是一种很难实现的样式,很多开发者会直接用梯形背景图片来生成效果,但是采用背景图片的方式产生了额外的http请求,并不是一种非常理想的方式,这里笔者为大家带来一种直接用css来实现梯形效果的方法。   以一个简···

    windows下忘记MySQL密码的修改方法

    一、windows下修改MySQL密码的方法如果在Windows下忘记了MySQL的密码,可以这样做:1.关闭正在运行的MySQL服务:net stop mysql或 在windows 任务管理器中结束 mysqld.exe 进程或在 管理工具里面的服务找到 mysql服务 ,将其停止;复制代码 代码如下:C:\Users\Administra···