译]JavaScript响应式的最佳解释
原文地址: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
随着price
或quantity
更新而更新。我们想要:
不幸的是,JavaScript是程序性的,而不是响应式的,所以这在现实中不起作用。为了实现total
响应,我们必须使用JavaScript来使事情表现得与众不同。
⚠️ 问题
我们需要保存如何计算得到total
,这样可以在price
或quantity
更新时重新运行它,
✅ 解决方案
首先,需要一些方法告诉我们的应用程序,“我即将运行的代码,存储它,我可能需要在其他时间运行它。”然后我们开始运行代码,如果price
或quantity
变量得到更新,再次运行存储的代码。
我们可以通过记录函数来执行此操作,以便我们可以再次运行它。
请注意,我们在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
。在继续之前,将数据设为对象属性。
假设每个属性(price
和quantity
)都有自己的内部Dep
类。
现在我们执行时:
由于访问了data.price
的值,我希望price
属性的Dep
类将存储在target
的匿名函数推送到其subscriber
数组(通过调用dep.depend()
)。由于data.quantity
被访问,我还希望quantity
属性Dep
类将存储在target
的匿名函数推送到其subscriber
数组中。
如果我有另一个匿名函数,只是data.price
被访问,我希望它只是推送到price
属性Dep
类。
price
的 subscribers
什么时候调用dep.notify()
?我希望在price
设置时调用它们。在文章的最后,我希望能够进入控制台并执行:
我们需要一些方式来挂钩数据属性(如price
或quantity
),所以当它被访问时,我们可以保存target
到我们的 subscribers
数组中,当它被更改时,运行存储在 subscribers
数组中的函数。
✅ 解决方案:Object.defineProperty()
我们需要了解Object.defineProperty()函数,它是简单的ES5 JavaScript。它允许我们为属性定义getter
和setter
函数。在我向您展示如何在Dep
类中使用它之前,将向您展示最基本的用法。
如您所见,它只记录两行。但是,它实际上没有get
或set
任何值,因为我们过度使用了该功能。我们现在加回来吧。get()
期望返回一个值,set()
仍然需要更新一个值,所以让我们添加一个internalValue
变量来存储我们当前的price
值。
既然我们的get
和set
工作正常,您认为将打印到控制台的是什么?
因此,当我们get
并set
值时,我们可以获得通知。通过一些递归,我们可以为数据数组中的所有项运行它,对吧?
Object.keys(data)
返回对象键的数组。
现在一切都有getter
和setter
,我们在控制台上看到了这一点。
? 将两种想法放在一起
当像这样的一段代码运行并getprice
值时,我们想要price
记住这个匿名函数(target)
。这样,如果price
被更改,或者set为新值,它将触发此函数以重新运行,因为它知道此行依赖于它。
Get =>记住当前匿名函数,当我们的值发生变化时,会再次运行它。
Set =>运行保存的匿名函数,我们的值随之改变。
或者就我们的Dep Class
而言
Price accessed (get) =>调用dep.depend()
以保存当前target
Price set =>调用dep.notify()
给price
,重新运行全部targets
让我们结合这两个想法,并完成我们的最终代码。
现在看看我们执行时会发生什么。
正是我们所希望的!price
和quantity
确实都响应了!每当值price
或quantity
更新时,全部的代码都会重新运行。
Vue文档中的这个插图现在应该开始有意义了。
你看到那个美丽的紫色数据圈getters and setters
了吗?看起来应该很熟悉!每个组件实例都有一个watcher
实例(蓝色),它从getter
(红线)收集依赖项。稍后调用setter
时,它会通知watcher
导致组件重新渲染。注释之后的图如下。
是的,这现在不是更有意义吗?
显然,Vue如何做到这一点更复杂,但你现在知道了基础知识。
⏪ 那么我们学到了什么?
标签:
相关文章
-
无相关信息