素材巴巴 > 程序开发 >

译]JavaScript响应式的最佳解释

程序开发 2023-09-12 22:53:50

 

原文地址:The Best Explanation of JavaScript Reactivity

转载地址:https://juejin.im/post/5bac7405f265da0af93b097e

许多前端JavaScript框架(例如Angular,React和Vue)都有自己的Reactivity引擎。通过了解响应式及其工作原理,您可以提高开发技能并更有效地使用JavaScript框架。在视频和下面的文章中,我们构建了您在Vue源代码中看到的相同类型的Reactivity。

?响应式系统

当你第一次看到它时,Vue的响应式系统看起来很神奇。拿这个简单的Vue应用程序来说:

 

 

不知何故Vue只是知道如果price改变,它应该做三件事:

 

我们解决一个大问题是通常不会这样编程。例如,如果我运行如下代码:

 

 

你觉得它打印什么?由于我们没有使用Vue,它将打印出来10

 

 

 

在Vue中,我们希望total随着pricequantity更新而更新。我们想要:

 

 

 

不幸的是,JavaScript是程序性的,而不是响应式的,所以这在现实中不起作用。为了实现total响应,我们必须使用JavaScript来使事情表现得与众不同。

⚠️ 问题

我们需要保存如何计算得到total,这样可以在pricequantity更新时重新运行它,

✅ 解决方案

首先,需要一些方法告诉我们的应用程序,“我即将运行的代码,存储它,我可能需要在其他时间运行它。”然后我们开始运行代码,如果pricequantity变量得到更新,再次运行存储的代码。

 

我们可以通过记录函数来执行此操作,以便我们可以再次运行它。

 

请注意,我们在target变量中存储了一个匿名函数,然后调用一个record函数。使用ES6箭头语法我也可以这样写:

 

这个record函数定义很简单:

 

我们正在存储target(在我们的例子中{ total = price * quantity }),所以我们可以稍后运行它,可以通过一个replay函数来运行存储的所有内容。

 

这将遍历执行storage 数组中存储的所有匿名函数。 然后在代码中,我们可以:

 

很简单吧?如果您需要阅读并尝试再次理解它,这里有完整的代码,仅供参考,如果您想知道原因,我会以特定的方式对此进行编码。

 

 

 

 

 

⚠️ 问题

我们可以根据需要继续记录target,但是有一个更强大的解决方案可以扩展我们的应用程序。一个负责维护target列表的类,当需要它们重新运行时,这些target列表会得到通知。

✅ 解决方案:依赖类

我们解决这个问题的一种方法是将这种行为封装到它自己的类中,这是一个实现标准观察者模式的依赖类。

因此,如果我们创建一个JavaScript类来管理我们的依赖项(它更接近Vue的处理方式),它可能看起来像这样:

 

请注意,我们现在存储匿名函数是subscribers而不是storage。我们现在调用的函数是depend而不是record,我们现在使用notify而不是replay。为了让这个运行:

 

它仍然有效,现在的代码感觉更可重用。唯一仍然感觉有点奇怪的是设置和运行target

 

⚠️ 问题

我们将为每个变量设置一个Dep类,并且很好地封装了创建需要监视更新的匿名函数的行为。也许一个watcher函数可能是为了处理这种行为。

所以不要这样调用:

 

(这只是上面的代码) 我们可以改为:

 

 

✅ 解决方案:观察者函数

在我们的Watcher函数中,我们可以做一些简单的事情:

 

如您所见,该watcher函数接受一个myFunc参数,将其设置为全局target属性,调用dep.depend()target添加到订阅者subscriber,执行target函数,然后重置target函数。

 

现在,当我们运行以下内容时:

 

 

您可能想知道为什么我们将target设为全局变量,而不是将其传递到我们需要的函数中。这将在我们的文章结尾处解释。

 

⚠️ 问题

我们有一个单独的Dep class,但我们真正想要的是每个变量都有自己的Dep。在继续之前,将数据设为对象属性。

 

假设每个属性(pricequantity)都有自己的内部Dep类。

 

现在我们执行时:

 

由于访问了data.price的值,我希望price属性的Dep类将存储在target的匿名函数推送到其subscriber 数组(通过调用dep.depend())。由于data.quantity被访问,我还希望quantity属性Dep类将存储在target的匿名函数推送到其subscriber 数组中。

 

如果我有另一个匿名函数,只是data.price被访问,我希望它只是推送到price属性Dep类。

 

price的 subscribers什么时候调用dep.notify()?我希望在price设置时调用它们。在文章的最后,我希望能够进入控制台并执行:

 

我们需要一些方式来挂钩数据属性(如pricequantity),所以当它被访问时,我们可以保存target到我们的 subscribers数组中,当它被更改时,运行存储在 subscribers数组中的函数。

 

✅ 解决方案:Object.defineProperty()

我们需要了解Object.defineProperty()函数,它是简单的ES5 JavaScript。它允许我们为属性定义gettersetter函数。在我向您展示如何在Dep类中使用它之前,将向您展示最基本的用法。

 

 

 

 

 

 

如您所见,它只记录两行。但是,它实际上没有getset任何值,因为我们过度使用了该功能。我们现在加回来吧。get()期望返回一个值,set()仍然需要更新一个值,所以让我们添加一个internalValue变量来存储我们当前的price值。

 

 

 

既然我们的getset工作正常,您认为将打印到控制台的是什么?

 

 

因此,当我们getset值时,我们可以获得通知。通过一些递归,我们可以为数据数组中的所有项运行它,对吧?

 

Object.keys(data)返回对象键的数组。

 

 

 

现在一切都有gettersetter,我们在控制台上看到了这一点。

 

 

 

? 将两种想法放在一起

 

 

当像这样的一段代码运行并getprice值时,我们想要price记住这个匿名函数(target)。这样,如果price被更改,或者set为新值,它将触发此函数以重新运行,因为它知道此行依赖于它。

 

Get =>记住当前匿名函数,当我们的值发生变化时,会再次运行它。

Set =>运行保存的匿名函数,我们的值随之改变。

或者就我们的Dep Class而言

Price accessed (get) =>调用dep.depend()以保存当前target

Price set =>调用dep.notify()price,重新运行全部targets

让我们结合这两个想法,并完成我们的最终代码。

 

 

 

现在看看我们执行时会发生什么。

 

 

 

正是我们所希望的!pricequantity确实都响应了!每当值pricequantity更新时,全部的代码都会重新运行。

Vue文档中的这个插图现在应该开始有意义了。

 

 

 

你看到那个美丽的紫色数据圈getters and setters了吗?看起来应该很熟悉!每个组件实例都有一个watcher实例(蓝色),它从getter(红线)收集依赖项。稍后调用setter时,它会通知watcher导致组件重新渲染。注释之后的图如下。

 

 

 

是的,这现在不是更有意义吗?

显然,Vue如何做到这一点更复杂,但你现在知道了基础知识。

⏪ 那么我们学到了什么?


标签:

素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。