[译]JavaScript响应式的最佳解释

原文地址:The Best Explanation of JavaScript Reactivityjavascript

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

💡响应式系统

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

不知何故 Vue只是知道若是 price改变,它应该作三件事:

  • 更新网页上price的值。
  • 从新计算乘法表达式price * quantity,并更新页面。
  • 再次调用函数totalPriceWithTax并更新页面。 可是等等,你是否会好奇,Vue如何知道price更改时所要更新的内容,以及它如何跟踪全部内容?
    这并非JavaScript一般的工做方式

咱们解决一个大问题是一般不会这样编程。例如,若是我运行以下代码: java

你以为它打印什么?因为咱们没有使用Vue,它将打印出来10react

在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类。
pricesubscribers何时调用 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,咱们在控制台上看到了这一点。

🛠 将两种想法放在一块儿

当像这样的一段代码运行并 get price值时,咱们想要 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如何作到这一点更复杂,但你如今知道了基础知识。

⏪ 那么咱们学到了什么?

  • 如何建立一个Dep类来收集依赖项(depend)并从新运行全部依赖项(notify)。
  • 如何建立一个Watcher程序来管理正在运行的代码,这些代码可能须要做为依赖项(target)被添加。
  • 如何使用Object.defineProperty()建立gettersetter

扩展

能够看看reactivity and proxies

相关文章
相关标签/搜索