许多前端JavaScript框架(如Angular、React、Vue)都有自己的反应性(Reactivity)引擎。了解Reactive是什么以及它是如何工作的可以提高您的开发技能并让您更有效地使用JavaScript。在本文中,我们构建了与Vue源代码相同的响应式功能。响应式系统当您第一次看到Vue的响应式系统时,您可能会觉得有点神奇。以下面这个简单的Vue应用为例:由于某种原因,Vue可以知道price的值是否发生了变化,并且在发生变化时可以完成以下三件事:更新网页中price的值;重新计算乘法表达式公式价格*数量,并更新页面;再次调用totalPriceWithTax函数并更新页面。但是等等,我好像听到你想问,Vue是如何知道价格变化时要更新哪些值的,又是如何跟踪一切的?这不是JavaScript编程通常的工作方式。如果这对您来说听起来不直观,那么我们需要了解程序通常不会以这种方式运行。例如,如果我运行以下示例代码:猜猜会打印什么?由于我们没有使用Vue,它将打印10:在Vue中,我们希望在价格或数量更新时更新总数。我们期望的输出是:不幸的是,JavaScript是过程性的,而不是反应性的,所以这在实际代码中是不可行的。为了完全响应,我们必须让JavaScript语言表现得不同。问题我们需要记住如何计算总数,以便我们可以在价格或数量发生变化时重新运行它。解决方案首先,我们需要有某种方式来告诉我们的应用程序,“我要运行的代码是什么,存储它,稍后我可能需要你运行它”。然后,我们运行代码,当价格或数量变量发生变化时,再次运行存储的代码:我们想到的方式可能是记录函数的内容,以便再次运行:注意我们在目标变量存储一个匿名函数,然后调用记录函数。如果我使用ES6的箭头语法,我也可以写成下面的形式:record的定义很简单:我们存储目标(在我们的例子中是{total=price*quantity}),这样我们可以在之后运行它,可能带有重放功能,运行我们记录的所有内容。这将遍历我们存储在存储数组中的所有匿名函数并运行它们。然后在我们的代码中,只是:很简单,对吧?如果你想通读代码并重试,下面给出了完整的代码。问题我们可以根据需要继续记录目标,但更好的方法是拥有一个可以扩展我们的应用程序的强大解决方案。我们可以使用一个类来维护目标列表并在需要重新运行时收到通知。解决方案:依赖类为了解决这个问题,我们将这些行为封装到单独的类中,使用一个依赖类(DependencyClass)来实现标准的观察者模式编程。如果我们创建JavaScript类来管理依赖项(类似于Vue的做法),它可能看起来像这样:注意,我们这里不再使用存储,而是使用订阅者来存储匿名函数,并且不再使用记录函数,而是调用depend,并使用通知而不是重播。要让它工作,只需:它仍然可以工作,并且我们的代码看起来有点可重用。唯一感觉特别奇怪的是设置和运行目标。问题以后每个类都会有一个Dep类。如果能封装创建匿名函数观察更新的行为就更好了。接下来,watcher函数将发挥作用来处理此行为。所以,我们不是调用:(这是上面例子的代码)而是调用:myFunc变量作为我们的全局目标属性,调用dep.depend(),它将我们的目标添加为订阅者,调用目标函数并重置目标。现在,我们可以运行下面的代码:您可能想知道为什么我们将target实现为全局变量而不是将其传递给所需的函数。这是有一定原因的,看完本文,相信你就会明白了。问题我们现在有了一个Dep类,但我们真正想要实现的是让每个变量都有自己的Dep。在进行下一步之前,让我们将它们放入属性中。让我们首先假设每个属性(价格和数量)都有自己的内部Dep类。现在,当我们运行时:因为我们可以访问data.price的值,所以我希望price属性的Dep类将我们的匿名函数(存储在target中)放入其订阅??数组中(通过调用dep.depend()).因为也访问了data.quantity,所以我希望quantity属性的Dep类将匿名函数(存放在target中)放到它的订阅数组中:如果我有其他只访问data.price的匿名函数,我希望把这个函数进入价格属性的Dep类。那么,我应该什么时候为价格订阅者调用dep.notify()呢?答案是在为价格赋值时。在这篇文章的最后,我希望能够在命令行实现如下效果:我们希望有一些方法来嵌入数据属性(价格或数量),这样在访问该属性时,目标可以是存储到订阅者数组,当属性发生变化时,可以运行存储在订阅者数组中的函数。解决方案:Object.defineProperty()我们需要学习ES5JavaScript提供的Object.defineProperty()函数。它允许我们为属性定义getter和setter函数。在展示如何使用Dep类之前,让我们先看看它的基本用法。可以看到,这里只打印了两条日志。但是,它实际上并没有获取和设置值,因为我们覆盖了该功能。现在,我们重新添加功能。get()预计会返回一个值,而set()仍然需要更新值,所以我们添加一个internalValue变量来存储当前价格值。我们的get和set可以正常工作,你猜控制台打印的信息会是什么?所以,我们有办法在获取和设置值时得到通知。通过一些递归,我们可以将其应用于数据数组中的所有条目。值得一提的是,Object.keys(data)可以返回对象中的键数组。现在所有属性都有getter和setter,让我们看一下控制台:将这两个想法放在一起当这样的代码运行并试图获取price属性的值时,我们希望price记住这个匿名函数(target)。这样,如果价格发生变化或被设置为新值,该函数可以重新运行,因为它知道这行代码取决于该属性。所以,你可以这样想。Get=>记住匿名函数,当值改变时我们会重新运行。Set=>运行保存的匿名函数,我们的值就会改变。或者,在DepClass的情况下:Priceaccess(get)=>调用dep.depend()来保存当前目标;价格设置=>调用价格的dep.notify(),并重新运行所有目标。接下来,我们将这两个想法结合起来,看看生成的代码。当我们运行时,看看控制台输出:正是我们所期望的!价格和数量现在都是被动的。当价格或数量的值更新时,我们的代码将完全重新运行。Vue文档中的图表对您来说应该非常清楚。看到漂亮的数据圈中的getter和setter了吗?看起来很熟悉!每个组件实例都有一个watcher(蓝色圆圈),它从getters(红线)收集依赖项。当setter随后被调用时,它会通知watcher,这会导致组件重新渲染。下面的图片有我自己添加的一些注释。现在,是不是感觉一目了然?当然,Vue的底层处理更复杂,但是你已经掌握了基础知识。我们学到了什么?如何创建一个Dep来收集依赖(depend)并重新运行所有依赖(notify);如何创建一个watcher来管理我们运行的代码,可能需要添加为依赖(target);如何使用Object.defineProperty()创建getter和setter。原文链接https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d
