百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

最简化 VUE的响应式原理

bigegpt 2024-08-29 11:26 4 浏览

前言

前端目前两个当家花旦框架 VUE React,它们能够流行开来,响应式原理做出了巨大贡献。毕竟,它通过数据的变更就能够更新相应的视图,极大的将我们从繁琐的DOM操作中解放出来。

所以掌握它们的响应式原理,对掌握前端框架的精髓就很重要了。

本文用最简单的方式来解释VUE2 最重点的响应式原理,看不懂算我输!

一、 响应式原理

什么是响应式原理?

意思就是在改变数据的时候,视图会跟着更新。这意味着你只需要进行数据的管理,给我们搬砖提供了很大的便利。React也有这种特性,但是React的响应式方式跟VUE完全不同。

React是通过this.setState去改变数据,然后根据新的数据重新渲染出虚拟DOM,最后通过对比虚拟DOM找到需要更新的节点进行更新。

也就是说React是依靠着虚拟DOM以及DOM的diff算法做到这一点的。而关于React这方面的文章,我已经写了很多了,还不了解的同学可以自行复习一下。

而VUE则是利用了Object.defineProperty的方法里面的setter 与getter方法的观察者模式来实现。

所以在学习VUE的响应式原理之前,先学习两个预备知识:Object.defineProperty 与 观察者模式。

如果你已经掌握了,可以直接跳到第三part。

二、预备知识

2.1 Object.defineProperty

这个方法就是在一个对象上定义一个新的属性,或者改变一个对象现有的属性,并且返回这个对象。里面有两个字段 set,get。顾名思义,set都是取设置属性的值,而get就是获取属性的值。

举个栗子:

// 在对象中添加一个属性与存取描述符的示例
var bValue;
var o = {};
Object.defineProperty(o, "b", {
  get : function(){
    console.log('监听正在获取b')
    return bValue;
  },
  set : function(newValue){
    console.log('监听正在设置b')
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});

o.b = 38;
console.log(o.b)

最终打印

监听正在设置b
监听正在获取b
38

从在上述例子中,可以看到当我们对 o.b 赋值38的时候,就会调用set函数,这时候给bValue赋值,之后我们就可以通过o.b来获取这个值,这时候,get函数被调用。

掌握到这一步,我们已经可以实现一个极简的VUE双向绑定了。

<input type="text" id="txt" />
<span id="sp"></span>

<script>
var txt = document.getElementById('txt'),
    sp = document.getElementById('sp'),
    obj = {}

// 给对象obj添加msg属性,并设置setter访问器
Object.defineProperty(obj, 'msg', {
  // 设置 obj.msg  当obj.msg反生改变时set方法将会被调用  
  set: function (newVal) {
    // 当obj.msg被赋值时 同时设置给 input/span
    txt.value = newVal
    sp.innerText = newVal
  }
})

// 监听文本框的改变 当文本框输入内容时 改变obj.msg
txt.addEventListener('keyup', function (event) {
  obj.msg = event.target.value
})
</script>

VUE给data里所有的属性加上set,get这个过程就叫做Reactive化

2.2 观察者模式

什么是观察者模式?它分为注册环节跟发布环节

比如我去买芝士蛋糕,但是店家还没有做出来。这时候我又不想在店外面傻傻等,我就需要隔一段时间来回来问问蛋糕做好没,对于我来说是很麻烦的事情,说不定我就懒得买了。

店家肯定想要做生意,不想流失我这个吃货客户。于是,在蛋糕没有做好的这段时间,有客户来,他们就让客户把自己的电话留下,这就是观察者模式中的注册环节。然后蛋糕做好之后,一次性通知所有记录了的客户,这就是观察者的发布环节

这里来简单实现一个观察者模式的类

function Observer() {
  this.dep = [];
  
  register(fn) {
    this.dep.push(fn)
  }
  
  notify() {
    this.dep.forEach(item => item())
  }
}

const wantCake = new Oberver();
// 每来一个顾客就注册一个想执行的函数
wantCake.register(() => {'console.log("call daisy")'})
wantCake.register(() => {'console.log("call anny")'})
wantCake.register(() => {'console.log("call sunny")'})

// 最后蛋糕做好之后,通知所有的客户
wantCake.notify()

三、原理解析

在学完了前面的铺垫之后,我们终于可以开始讲解VUE的响应式原理了。

官网用了一张图来表示这个过程,但是刚开始看可能看不懂,等到文章的最后,我们再来看,应该就能看懂了。



总共分为三步骤:1、init 阶段: VUE 的 data的属性都会被reactive化,也就是加上 setter/getter函数。

function defineReactive(obj: Object, key: string, ...) {
    const dep = new Dep()

    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        ....
        dep.depend()
        return value
        ....
      },
      set: function reactiveSetter (newVal) {
        ...
        val = newVal
        dep.notify()
        ...
      }
    })
  }
  
  class Dep {
      static target: ?Watcher;
      subs: Array<Watcher>;

      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }

      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }

其中这里的Dep就是一个观察者类,每一个data的属性都会有一个dep对象。当getter调用的时候,去dep里注册函数,至于注册了什么函数,我们等会再说。

setter的时候,就是去通知执行刚刚注册的函数。

2、mount 阶段:

mountComponent(vm: Component, el: ?Element, ...) {
    vm.$el = el

    ...

    updateComponent = () => {
      vm._update(vm._render(), ...)
    }

    new Watcher(vm, updateComponent, ...)
    ...
}

class Watcher {
  getter: Function;

  // 代码经过简化
  constructor(vm: Component, expOrFn: string | Function, ...) {
    ...
    this.getter = expOrFn
    Dep.target = this                      // 注意这里将当前的Watcher赋值给了Dep.target
    this.value = this.getter.call(vm, vm)  // 调用组件的更新函数
    ...
  }
}

mount 阶段的时候,会创建一个Watcher类的对象。这个Watcher实际上是连接Vue组件与Dep的桥梁。每一个Watcher对应一个vue component。

这里可以看出new Watcher的时候,constructor 里的this.getter.call(vm, vm)函数会被执行。getter就是updateComponent。这个函数会调用组件的render函数来更新重新渲染。

而render函数里,会访问data的属性,比如

render: function (createElement) {
  return createElement('h1', this.blogTitle)
}

此时会去调用这个属性blogTitle的getter函数,即:

// getter函数
get: function reactiveGetter () {
    ....
    dep.depend()
    return value
    ....
 },

// dep的depend函数
depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
}

在depend的函数里,Dep.target就是watcher本身(我们在class Watch里讲过,不记得可以往上第三段代码),这里做的事情就是给blogTitle注册了Watcher这个对象。这样每次render一个vue 组件的时候,如果这个组件用到了blogTitle,那么这个组件相对应的Watcher对象都会被注册到blogTitle的Dep中。

这个过程就叫做依赖收集

收集完所有依赖blogTitle属性的组件所对应的Watcher之后,当它发生改变的时候,就会去通知Watcher更新关联的组件。

3、更新阶段:

当blogTitle 发生改变的时候,就去调用Dep的notify函数,然后通知所有的Watcher调用update函数更新。

notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
}

可以用一张图来表示:


由此图我们可以看出Watcher是连接VUE component 跟 data属性的桥梁。

总结

最后,我们通过解释官方的图来做个总结。


1、第一步:组件初始化的时候,先给每一个Data属性都注册getter,setter,也就是reactive化。然后再new 一个自己的Watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DOM。在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的Watcher函数注册进sub里。


2、第二步:当data属性发生改变之后,就会遍历sub里所有的watcher对象,通知它们去重新渲染组件。

整个过程就是那么简单啦~

彩蛋

本来这篇文章应该已经结束了,但是我们既然已经学会了响应式原理,那当然要对目前vue的一些规则做点解释啦。算是赠送的彩蛋~。

  • 如果你想要属性是响应式的,就一定要写在data对象里。因为VUE只对data里的属性做reactive化处理。
var vm = new Vue({
  data:{
    a:1
  }
})

// `vm.a` 是响应的

vm.b = 2
// `vm.b` 是非响应的

或者使用Vue.set(vm.someObject, 'b', 2)动态添加。

参考文档:

1、https://zhuanlan.zhihu.com/p/678939362、https://www.njleonzhang.com/2018/09/26/vue-reactive.html3、https://vuejs.bootcss.com/v2/gu

相关推荐

恢复软件6款汇总推荐,帮你减轻数据恢复压力!

在当今数字化生活中,数据丢失的风险如影随形。无论是误删文件、硬盘故障,还是遭遇病毒攻击,丢失的数据都可能给我们带来不小的麻烦。此时,一款优秀的数据恢复软件就成为了挽救数据的关键。今天,为大家汇总推荐...

中兴星星一号刷回官方原版recovery的教程

【搞科技教程】中兴星星一号的官方recovery也来说一下了,因为之前给大家分享过了第三方的recovery了,之前给大家分享的第三方recovery也是采用一键刷入的方式,如果细心的朋友会发现,之前...

新玩机工具箱,Uotan柚坛工具箱软件体验

以前的手机系统功能比较单调,各厂商的重视程度不一样,所以喜欢玩机的朋友会解锁手机系统的读写权限,来进行刷机或者ROOT之类的操作,让使用体验更好。随着现在的手机系统越来越保守,以及自身功能的增强,...

三星g906k刷recovery教程_三星g906k中文recovery下载

【搞科技教程】看到有一些机友在找三星g906k的第三方recovery,下面就来说一下详细的recovery的刷入方法了,因为手机只有有了第三方的recovery之后才可以刷第三方的root包和系统包...

中兴星星2号刷recovery教程_星星二号中文recovery下载

【搞科技教程】咱们的中兴星星2手机也就是中兴星星二号手机的第三方recovery已经出来了,并且是中文版的,有了这个recovery之后,咱们的手机就可以轻松的刷第三方的系统包了,如果没有第三方的re...

数据恢复软件有哪些值得推荐?这 6 款亲测好用的工具汇总请收好!

在数字生活中,数据丢失的阴霾常常突如其来。无论是误删工作文档、格式化重要磁盘,还是遭遇系统崩溃,都可能让我们陷入焦虑。关键时刻,一款得力的数据恢复软件便是那根“救命稻草”。今天,为大家精心汇总6...

中兴u956刷入recovery的教程(中兴e5900刷机)

【搞科技教程】这次主要来给大家说说中兴u956手机如何刷入第三方的recovery,因为第三方的recovery工具是咱们刷第三方rom包的基础,可是很我欠却不会刷,所以太这里来给大家整理了一下详细的...

联想A850+刷recovery教程 联想A850+第三方recovery下载

【搞科技教程】联想A850+的第三方recovery出来了,这个第三方的recovery是非常的重要的,比如咱们的手机要刷第三方的系统包的时候,都是需要用到这个第三方的recovery的,在网上也是有...

工具侠重大更新 智能机上刷机一条龙完成

工具侠是针对玩机的机油开发的一款工具,不管是发烧级别的粉丝,还是普通小白用户,都可以在工具侠上找到你喜欢的工具应用。这不,最新的工具侠2.0.16版本,更新了专门为小白准备的刷机助手工具,以及MTK超...

shift+delete删除的文件找回6种硬盘数据恢复工具

硬盘作为电脑的重要存储设备,如同一个巨大的数字仓库,承载着我们日常工作、学习和生活中的各种文件,从珍贵的照片、重要的工作文档到喜爱的视频、音乐等,都依赖硬盘来安全存放。但有时,我们可能会不小心用sh...

使用vscode+Deepseek 实现AI编程 基于Cline和continue

尊敬的诸位!我是一名专注于嵌入式开发的物联网工程师。关注我,持续分享最新物联网与AI资讯和开发实战。期望与您携手探寻物联网与AI的无尽可能。这两天deepseek3.0上线,据说编程能力比肩Cl...

详解如何使用VSCode搭建TypeScript环境(适合小白)

搭建Javascript环境因为TypeScript不能直接在浏览器上运行。它需要编译器来编译并生成JavaScript文件。所以需要首先安装好javascript环境,可以参考文章:https://...

使用VSCode来书写你的Jupyter Notebooks

现在你可以在VScode里面来书写你的notebook了,使用起来十分的方便。下面来给大家演示一下环境的搭建。首先需要安装一个jupyter的包,使用下面的命令安装:pip3install-ih...

使用VSCode模板提高Vue开发效率(vscode开发vue插件)

安装VSCode安装Vetur和VueHelper插件,安装完成后需要重启VScode。在扩展插件搜索框中找到如下Vetur和VueHelper两个插件,注意看图标。添加Vue模板打...

干货!VsCode接入DeepSeek实现AI编程的5种主流插件详解

AI大模型对编程的影响非常之大,可以说首当其冲,Cursor等对话式编程工具渐渐渗透到开发者的工作中,作为AI编程的明星产品,Cursor虽然好用,但是贵啊,所以咱们得找平替,最好免费那种。俗话说,不...