会员登录 - 用户注册 - 设为首页 - 加入收藏 - 网站地图 以Vue为例,解释JavaScript的反应性!

以Vue为例,解释JavaScript的反应性

时间:2025-11-05 13:53:09 来源:益强数据堂 作者:数据库 阅读:262次

很多前端 JavaScript 框架(如 Angular、例解React 和 Vue)都有自己的例解反应性(Reactivity)引擎。理解反应式是例解什么以及如何运行能够提升你的开发水平,同时能够更高效地使用 JavaScript。例解在本文中,例解我们构建了与 Vue 源码相同的例解反应性功能。

反应性系统

当你第一次见到 Vue 的例解反应性系统时,你可能会感觉有些神奇。例解以下面这个简单的例解 Vue 应用为例:

 

不知道基于什么原因,Vue 能够知道price的例解值是否发生了变化,并且在变化的例解时候能够完成如下三件事情:

更新 Web 页面price的值; 重新计算乘法表达式price * quantity,并更新页面; 再次调用totalPriceWithTax函数并更新页面。例解

但是例解,稍等,例解我似乎听到你想问,例解Vue 如何知道在price发生变化的时候都要更新哪些值,它又是如何跟踪所有的内容的呢?

 

这并不是 JavaScript 编程通常的运行方式。源码库

如果这对你来说不那么直观,那么我们需要明白程序通常并不是按照这种方式来运行的。例如,如果我运行下面的样例代码:

 

你猜将会打印出什么内容呢?因为我们没有使用 Vue,它将会打印出10:

 

在 Vue 中,我们想要在price或quantity更新的时候,total也进行更新。我们希望的输出是:

 

但令人遗憾的是,JavaScript 是过程性的,并不是反应式的,所以在现实代码并这并不可行。为了让total具有反应性,我们必须让 JavaScript 语言按照不同的方式来运行。

问题

我们需要记住如何计算total,这样才能在price或quantity发生变化的时候重新运行。

解决方案

首先,我们需要有某种方法告诉我们的应用,“我将要运行的香港云服务器代码是什么,将它存储起来,在稍后某个时间点我可能需要你运行它”。然后,我们运行代码,在price或quantity变量发生变化的时候,再次运行存储的代码:

 

我们想到的办法可能就是将函数的内容记录下来,这样就能再次运行了:

 

需要注意,我们在target变量中存储了一个匿名函数,然后调用了record函数。如果采用 ES6 的箭头语法的话,我还可以写成如下的形式:

 

record的定义非常简单:

 

我们将target存储了起来(我们的示例中也就是{ total = price * quantity }),这样的话,我们就能在随后运行它,可能会借助一个replay函数运行我们记录下来的所有内容。

 

这样会遍历我们在storage数组中存储的所有匿名函数,并运行它们。

那么在我们的b2b供应网代码中,只需:

 

非常简单,对吧?如果你想要通读代码并再次尝试的话,下面给出了完整的代码。

 

问题

我们可以按需继续记录 target,但是更好的方式是有一种健壮的方案,能够扩展我们的应用。我们可以使用一个类,让这个类维护一个 target 的列表,当需要它们重新运行的时候,这个类会得到通知。

解决方案:依赖类

要解决这个问题,我们将这些行为封装到单独的类中,使用一个依赖类(Dependency Class)来实现标准的观察者模式编程。

如果我们创建 JavaScript 类来管理依赖的话(类似于 Vue 的处理方式),它看起来可能会如下所示:

 

需要注意,我们这里不再使用storage,而是使用subscribers来存储匿名函数,也不再使用record函数了,而是调用depend,同时使用notify代替了replay。要让它运行起来,只需:

 

它依然可以运行,而且我们的代码看上去具备了一定的可重用性。唯一感觉尤其诡异的地方就是设置和运行target。

问题

未来,每个类都会有一个 Dep 类,如果能将创建匿名函数观察更新的行为封装起来就更好了。接下来,watcher函数将会出场来负责这种行为。

所以,我们将不会再调用:

 

(这就是上面示例的代码)

相反,我们只需这样调用:

 

解决方案:Watcher 函数

在 Watcher 函数中,我们可以做几件很简单的事情:

 

可以看到,watcher函数接受一个myFunc变量,将其作为我们的全局target属性,调用dep.depend(),将会以订阅者的形式添加我们的 target,调用target函数并重置target。

现在,我们可以运行下面的代码:

你可能会想,我们为什么要将target实现为全局变量,而不是将其传递给所需的函数。这里有一定的原因,在本文结束的时候,相信你就明白了。

问题

我们现在有了一个Dep类,但是我们真正想要实现的是每个变量都有自己的 Dep。在进行下一步讲解之前,我们先将它们放到属性中。

 

我们先假设每个属性(price和quantity)都有其自己的内部 Dep 类。

 

现在,当我们运行:

 

因为访问到了data.price的值,所以我希望price属性的 Dep 类要将我们的匿名函数(存储在target中)放到它的订阅数组中(通过调用dep.depend())。因为data.quantity也被访问到了,所以我希望quantity属性的 Dep 类要将该匿名函数(存储在target中)放到它的订阅数组中:

 

如果我还有其他的匿名函数只访问data.price的话,我希望要将这个函数放到price属性的 Dep 类中。

 

那么,我该在何时为price的订阅者调用dep.notify()呢?答案是为price赋值的时候。在本文结束的时候,我希望能够在命令行中实现如下的效果:

 

我们希望能有某种方式嵌入到数据属性中(price或quantity),这样的话,当属性被访问的时候,能够将target存储到订阅者数组中,当属性变更时,能够运行存储在订阅者数组中的函数。

解决方案:Object.defineProperty()

我们需要学习 ES 5 JavaScript 所提供 Object.defineProperty() 函数。它允许我们为属性定义 getter 和 setter 函数。在展示如何与 Dep 类协作之前,我们看一下它的基础用法。

  

可以看到,这里只是打印了两条日志。但是,它并没有实际get和set值,这是因为我们将功能覆盖掉了。现在,我们将功能添加回来。get()预期要返回一个值,而set()依然要更新值,所以我们添加一个internalValue变量来存储当前的price值。

 

我们的get和set都能正常运行了,你觉得控制台的打印信息会是什么呢?

 

所以,当取值和设置值的时候,我们有了一种得到提醒的方法。借助一些递归,我们就可以将其用到数据数组的所有条目中了。

值得一提的是,Object.keys(data)能够返回对象中 key 所组成的数组。

 

现在,所有的属性都有 getter 和 setter 了,我们来看一下控制台:

 

 将这两个理念组合在一起

 

当这样的代码运行并尝试 get price属性的值时,我们希望price能够记住这个匿名函数(target)。通过这种方式,如果price发生了变化,或者被set了一个新的值,这个函数就能重新运行,因为它能够知道这行代码依赖该属性。所以,你可以按照如下的方式来思考。

Get=>记住该匿名函数,当值发生变化的时候我们会重新运行。

Set=>运行保存的匿名函数,我们的值就会发生变化。

或者,在 Dep Class 的场景下:

Price 访问 (get) =>调用dep.depend()保存当前的target;

Price set =>调用 price 的dep.notify(),重新运行所有的target。

接下来,我们将这两个理念组合起来,并看一下最终的代码。

 

在我们运行的时候,看一下控制台的输出:

 

完全符合我们的预期!现在price和quantity都是反应式的了。当price或quantity的值更新时,我们的代码完全重新运行了。

Vue 文档中的图示对你来说应该就非常清晰了。

 

看到漂亮的 Data 圆圈中的 getters 和 setters 了吗?它看起来似曾相识!每个组件实例都有一个watcher(蓝色圆圈),它会从 getter 中收集依赖(红线)。当 setter 随后被调用时,它会 通知watcher,从而会导致组件的重新渲染。如下的图片添加了一些我自己的注释。

 

现在,是不是感觉一目了然了呢?

当然,Vue 底层的处理要更复杂,但是你现在已经掌握了它的基础。

我们学到了什么呢?

如何创建 Dep 来收集依赖(depend)并重新运行所有的依赖(notify); 如何创建 watcher 来管理我们正在运行的代码,这些代码可能需要作为依赖添加进来(target); 如何使用 Object.defineProperty() 来创建 getter 和 setter。

原文链接

https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d 

(责任编辑:数据库)

推荐内容
  • 说明:系统:Ubuntu Server 11.10系统:Windows Server 2003################################################################################################### Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesnt use lo0# Accepts all established inbound connections# Allows all outbound traffic-A OUTPUT -j ACCEPT-A INPUT -p tcp --dport 80 -j ACCEPT-A INPUT -p tcp --dport 873 -j ACCEPT# THE -dport NUMBER IS THE SAME ONE YOU SET UP IN THE SSHD_CONFIG FILE# Now you should read up on iptables rules and consider whether ssh access# Allow ping# log iptables denied calls (access via dmesg command)# Reject all other inbound - default deny unless explicitly allowed policy:-A FORWARD -j REJECT##################################################################################################ctrl+o #保存ctrl+x #退出备注:873是Rsync端口iptables-restore < /etc/iptables.default.rules #使防火墙规则生效nano /etc/network/if-pre-up.d/iptables #创建文件,添加以下内容,使防火墙开机启动###########################################################!/bin/bashwhereis rsync #查看系统是否已安装rsync,出现下面的提示,说明已经安装ctrl+o #保存log file = /var/log/rsyncd.log #日志文件位置,启动rsync后自动产生这个文件,无需提前创建。/etc/init.d/rsync start #启动Next 下一步Next默认安装路径 C:Program FilescwRsyncInstall 安装Close 安装完成,关闭3、测试是否与Rsync服务端通信成功开始-运行-cmd输入cd C:Program FilescwRsyncbin 回车再输入telnet 192.168.21.168 873 回车出现下面的界面,说明与Rsync服务端通信成功备注 C:Program FilescwRsyncbin 是指cwRsync程序安装路径4、cwRsync客户端同步Rsync服务端的数据开始-运行-cmd,输入cd C:Program FilescwRsyncbin 回车再输入rsync -vzrtopg --progress --delete mysqlbakuser@192.168.21.168::MySQL_Backup /cygdrive/d/mysql_data输入密码:123456 回车出现下面的界面,说明数据同步成功可以打开D:mysql_data 与Rsync服务端/home/mysql_data目录中的数据对比一下,查看是否相同d/mysql_data 代表D:mysql_data192.168.21.168 #Rsync服务端IP地址-vzrtopg --progress #显示同步过程详细信息三、在cwRsync客户端的任务计划中添加批处理脚本文件,每天凌晨3:00钟自动同步Rsync服务端/home/mysql_data目录中的数据到D:mysql_data目录1、打开C:Program FilescwRsyncbin目录,新建passwd.txt输入123456保存继续在C:Program FilescwRsyncbin目录,新建MySQL_Backup.bat输入echo.echo.rsync -vzrtopg --port=873 --progress --delete mysqlbakuser@192.168.21.168::MySQL_Backup /cygdrive/d/mysql_data < passwd.txtecho 数据同步完成echo.最后保存退出2、添加批处理脚本到Windows任务计划开始-设置-控制面板-任务计划打开添加任务计划,下一步浏览,选择打开C:Program FilescwRsyncbin目录里面的MySQL_Backup.bat执行这个任务:选择每天,下一步起始时间:3:00运行这个任务:每天,下一步输入Windows系统管理员的登录密码,下一步完成扩展说明:假如要调整同步的时间,打开任务计划里面的MySQL_Backup切换到日程安排来选项设置,还可以打开高级来设置每隔几分钟运行一次MySQL_Backup.bat这个脚本至此,Ubuntu Server Rsync服务端与Windows cwRsync客户端实现数据同步完成
  • 77手机怎么样?(详细评测与用户反馈)
  • 锐龙Ryzen31200(一体化处理器为你的计算机带来全新体验)
  • 使用U盘启动装机教程(简单操作,轻松安装系统)
  • 探索Turbomail邮件服务的功能和优势(解析Turbomail邮件服务的特点及适用范围)
  • 电脑重装系统的详细教程(快速、简单、安全的操作步骤)
热点内容