许多前端 JavaScript 框架 (例如 Angular,React 和 vue) 都有自己的数据相应引擎. 通过了解相应性及其工作原理, 您可以提高开发技能并更有效地使用 JavaScript 框架. 在视频和下面的文章中, 我们构建了您在 Vue 源代码中看到的相同类型的 Reactivity.
如果您观看此视频而不是阅读文章, 请观看系列中的下一个视频, 与 Vue 的创建者 Evan You 讨论反应性和代理.
?? The Reactivity System
当你第一次看到它时, Vue 的响应系统看起来很神奇. 拿这个简单的 Vue 应用程序:
不知何故, Vue 只知道如果价格发生变化, 它应该做三件事:
更新我们网页上的价格值.
重新计算乘以 price * quantity 的表达式, 并更新页面.
再次调用 totalPriceWithTax 函数并更新页面.
但是等等, 你应该会觉得奇怪, 当价格变化时, Vue 如何知道要更新什么, 以及它如何跟踪所有内容?
这不是 JavaScript 编程常规的工作方式.
如果你不明白, 那我们试着看看常规的 JavaScript 是怎么运行的. 例如, 如果我运行此代码:
你觉得它打印什么? 由于我们没有使用 Vue, 它将打印 10.
在 Vue, 我们希望每当价格或数量更新时, 总计都会得到更新. 我们想要:
不幸的是, JavaScript 是程序性的, 而不是被动的, 所以这在现实生活中不起作用. 为了使数据变化得到相应, 我们必须使用 JavaScript 来使事情表现不同.
?? 问题
我们需要保存计算总数的方式, 以便在价格或数量变化时重新运行.
? 解决方案
首先, 我们需要一些方法告诉我们的应用程序,"我即将运行的代码, 存储它, 我可能需要你在另一个时间运行它." 然后我们将要运行代码, 如果价格或数量变量得到更新, 再次运行存储的代码.
请注意, 我们在目标变量中存储了一个匿名函数, 然后调用了一个记录函数. 使用 ES6 箭头语法我也可以这样写:
请注意, 我们在目标变量中存储了一个匿名函数, 然后调用了一个记录函数. 使用 ES6 箭头语法我也可以这样写:
记录的方法:
我们正在存储目标(在我们的例子中是{total = price * quantity}), 所以我们可以稍后运行它.
这将遍历存储阵列中存储的所有匿名函数并执行它们中的每一个.
然后在我们的代码中, 我们可以:
很简单吧? 如果您需要阅读并尝试再次掌握它, 这里的代码就完整了. 仅供参考, 如果您想知道原因, 我会以特定的方式对此进行编码.
?? 问题
我们可以根据需要继续记录目标, 但是有一个更强大的解决方案可以扩展我们的应用程序. 那就是一个负责维护目标列表的类, 当我们需要它们重新运行时, 这些目标列表会得到通知.
? 解决方法: 使用 Class
我们可以开始解决这个问题的一种方法是将这种行为封装到它自己的 Class 中, 这是一个实现标准编程观察者模式的依赖类.
因此, 如果我们创建一个 JavaScript 类来管理我们的依赖项(它更接近 Vue 处理事物的方式), 它可能看起来像这样:
让它运行:
它仍然有效, 现在我们的代码感觉更可靠了. 只有仍然感觉有点奇怪的是 target()的设置和运行.
?? 问题
我们将为每个变量设置一个 Dep 类, 并且很好地封装了创建需要监视更新的匿名函数的行为. 也许观察者功能可能是为了处理这种行为.
(这只是上面的代码)
我们可以改为:
? 解决方案: 观察者功能
在我们的 Watcher 功能中, 我们可以做一些简单的事情:
如您所见, watcher 函数接受 myFunc 参数, 将其设置为我们的全局目标属性, 调用 dep.depend()以将目标添加为订阅者, 调用目标函数并重置目标.
现在, 当我们运行以下内容时:
您可能想知道为什么我们将 target 实现为全局变量, 而不是将其传递到我们需要的函数中. 这有一个很好的理由, 这将在我们的文章结尾处揭晓.
?? 问题
我们有一个 Dep 类, 但我们真正想要的是每个变量都有自己的 Dep. 在我们继续之前, 先存储一下数据.
让我们假设我们的每个属性 (价格和数量) 都有自己的内部 Dep 类.
当我们运行时:
由于访问了 data.price 值, 我希望 price 属性的 Dep 类将我们的匿名函数 (存储在目标中) 推送到其订阅者数组 (通过调用 dep.depend()). 由于访问了 data.quantity, 我还希望 quantity 属性 Dep 类将此匿名函数(存储在目标中) 推送到其订阅者数组中.
如果我有另一个匿名函数, 只访问 data.price, 我希望只推送到价格属性 Dep 类.
我什么时候想要在价格订阅者上调用 dep.notify()? 我希望在设定价格时调用它们. 在文章的最后, 我希望能够进入控制台并执行:
我们需要一些方法来挂钩数据属性(如价格或数量), 所以当它被访问时我们可以将目标保存到我们的订阅者数组中, 当它被更改时, 运行存储在我们的订阅者数组中的函数.
? 解决方案: Object.defineProperty()
我们需要了解 Object.defineProperty()函数, 它是简单的 ES5 JavaScript. 它允许我们为属性定义 getter 和 setter 函数. 在我向您展示如何在 Dep 类中使用它之前, 先简单展示一下改函数的用法.
如您所见, 它只记录两行. 但是, 它实际上并没有获取或设置任何值, 因为我们过度使用了该功能. 我们现在加回来吧. get()期望返回一个值, 而 set()仍然需要更新一个值, 所以让我们添加一个 internalValue 变量来存储我们当前的价格值.
既然我们的 get 和 set 工作正常, 您认为将打印到控制台的是什么?
因此, 当我们获取并设置值时, 我们可以获得通知. 通过一些递归, 我们可以为数组中的所有项运行它
FYI,Object.keys(data)返回对象键的数组.
现在一切都有 getter 和 setter, 我们在控制台上看到了这一点.
?? Putting both ideas together
当像这样的一段代码运行并获得价格的价值时, 我们希望价格记住这个匿名函数(目标). 这样, 如果价格变化, 或者设置为新值, 它将触发此函数以重新运行, 因为它知道此行依赖于它. 所以你可以这样想.
Get =>记住这个匿名函数, 当我们的值发生变化时, 我们会再次运行它.
Set =>运行保存的匿名函数, 我们的值刚改变.
或者就我们的 Dep Class 而言
Price accessed (get) => 调用 dep.depend()来保存当前目标
Price set => 在价格上调用 dep.notify(), 重新运行所有目标
让我们结合这两个想法, 并完成我们的最终代码.
现在看看会发生什么.
正是我们所希望的! 价格和数量都确实是得到了实时的响应的! 只要价格或数量的价值得到更新, 我们的总代码就会重新运行.
Vue 文档中的这个插图现在应该开始有意义了.
你看到那个漂亮的紫色数据圈了吗? 看起来应该很眼熟! 每个组件实例都有一个从 getter(红线)收集依赖项的服务观察器实例(蓝色). 当稍后调用设置程序时, 它会通知监视程序, 它将导致组件重新呈现. 下面是我自己的一些注释的图片.
是的, 现在是不是觉得更有意义了.
显然, Vue 做的可能更复杂更惊喜, 但你现在知道了基础知识.
? 总结: 所以我们学了什么?
如何创建一个 Dep 类来收集依赖项 (依赖) 并重新运行所有依赖项(notify).
如何创建一个观察程序来管理我们正在运行的代码, 这些代码可能需要作为依赖项添加(target).
如何使用 Object.defineProperty()创建 getter 和 setter.
- <script type="text/javascript">
- let data = { price: 5, quantity: 2 };
- let target = null;
- class Dep {
- constructor() {
- this.subscribers = [];
- }
- depend() {
- if (target && !this.subscribers.includes(target)) {
- this.subscribers.push(target);
- }
- }
- notify() {
- this.subscribers.forEach(sub => sub());
- }
- }
- Object.keys(data).forEach(key => {
- let internalValue = data[key];
- let dep = new Dep();
- Object.defineProperty(data, key, {
- get() {
- //console.log("getting price :" + internalValue);
- dep.depend();
- return internalValue;
- },
- set(newVal) {
- // console.log("setting price to :" + newVal);
- internalValue = newVal;
- dep.notify();
- }
- })
- })
- function watcher(myFunc) {
- target = myFunc;
- target();
- target = null;
- }
- watcher(() => {
- data.total = data.price * data.quantity;
- console.log("total:" + data.total);
- })
- </script>
转载: https://www.zcfy.cc/article/the-best-explanation-of-javascript-reactivity
来源: http://www.bubuko.com/infodetail-2873908.html