9月阅读周·JavaScript异步编程设计快速响应的网络应用:深入理解JavaScript事件,事件化模型篇

举报
叶一一 发表于 2024/09/25 10:11:15 2024/09/25
【摘要】 背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效,已经坚持阅读七个月。已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScri...

背景

去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。

没有计划的阅读,收效甚微。

新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。

这个“玩法”虽然常见且板正,但是有效,已经坚持阅读七个月。

已读完书籍《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》

当前阅读周书籍《JavaScript异步编程设计快速响应的网络应用》

事件化模型

我们先来看看人气爆棚的Backbone.js框架。可以像这样创建一个新的Model(模型)对象:

style = new Backbone.Model(
{font:'Georgia'}
);

model作为参数时只是代表了那个简单的可以传递的JSON对象。

style.toJSON() // {"font":"Georgia"}

但不同于普通对象的是,这个model对象会在发生变化时发布通知。

style.on('change:font', function(model, font) {
alert('Thank you for choosing ' + font + '!');
});

老式的 JavaScript 依靠输入事件的处理器直接改变 DOM。新式的JavaScript先改变模型,接着由模型触发事件而导致DOM的更新。在几乎所有的应用程序中,这种关注层面的分离都会带来更优雅、更直观的代码。

模型事件的传播

作为最简形式,MVC三层架构只包括相互联系的模型和视图:“如果模型是这样变化的,那么DOM就要那样变化。”不过,MVC三层架构最大的利好出现在 change(变化)事件冒泡上溯数据树的时候。不用再去订阅数据树每片叶子上发生的事件,而只需订阅数据树根和枝处发生的事件即可。

事件化模型的set/get方法

正如我们知道的,JavaScript确实没有一种每当对象变化时就触发事件的机制。因此请记住,事件化模型要想工作的话,必须要使用一些像Backbone.js之set/get这样的方法。

style.set({font:'Palatino'}); // 触发器警报!
style.get('font');    // 结果为"Palatino"
style.font = 'Comic Sans';  // 未触发任何事件
style.font;      // 结果为"Comic Sans"
style.get('font');    // 结果仍为"Palatino"

将来也许无需如此,前提是名为Object.observe的ECMAScript提案已经获得广泛接纳。

为此,Backbone的Model对象常常组织成Backbone集合的形式,其本质是事件化数组。我们可以监听什么时候对这些数组增减了Model对象。Backbone集合可以自动传播其内蕴Model对象所发生的事件。

举个例子,假设有一个spriteCollection(精灵集合)集合对象包含了上百个Model对象,这些Model对象代表了要画在canvas(画布)元素上的一些东西。每当任意一个精灵发生变化,都需要重新绘制画布。我们不用逐个在那些精灵上附加 redraw(重绘)函数作为change事件的处理器,相反,只要写这样一行代码:

spriteCollection.on('change', redraw);

注意,集合事件的这种自动传播只能下传一层。Backbone没有嵌套式集合这样的概念。不过,我们可以自行用Backbone的trigger方法来实现嵌套式集合的多层传播。有了多层传播机制之后,任意的Backbone对象都可以触发任意的事件。

事件循环与嵌套式变化

从一个对象向另一个对象传播事件的过程提出了一些需要关注的问题。如果每次有个对象上的事件引发了一系列事件并最终对这个对象本身触发了相同的事件,则结果就是事件循环。如果这种事件循环还是同步的,那就造成了堆栈上溢。

然而在很多时候,变化事件的循环恰恰是我们想要的。最常见的情况就是双向绑定——两个模型的取值会彼此关联。假设我们想保证x始终等于2 * y。

var x = new Backbone.Model({value:0});
var y = new Backbone.Model({value:0});
x.on('change:value', function(x, xVal) { y.set({value:xVal / 2});});
y.on('change:value', function(y, yVal) { x.set({value:2 * yVal});});

你可能觉得当x或y的取值变化时,这段代码会导致无限循环。但实际上它相当安全,这要感谢Backbone中的两道保险。

  • 当新值等于旧值时,set 方法不会导致触发change 事件。
  • 模型正处于自身的change 事件期间时,不会再触发change 事件。

第二道保险代表了一种自保哲学。假设模型的一个变化导致同一个模型又一次变化。由于第二次变化被“嵌套”在第一次变化内部,所以这次变化的发生悄无声息。外面的观察者没有机会回应这种静默的变化。

很明显,在 Backbone中维持双向数据绑定是一个挑战。而另一个重要的MVC框架,即Ember.js,采用了一种完全不同的方式:双向绑定必须作显式声明。一个值发生变化时,另一个值会通过延时事件作异步更新。于是,在触发这个异步更新事件之前,应用程序的数据将一直处于不一致的状态。

多个事件化模型之间的数据绑定问题不存在简单的解决方案。在Backbone中,有一种审慎绕过这个问题的途径就是silent标志。如果在set方法中添加了{silent:true}选项,则不会触发change事件。因此,如果多个彼此纠结的模型需要同时进行更新,一个很好的解决方法就是悄无声息地设置它们的值。然后,当这些模型的状态已经一致时,才调用它们的change方法以触发对应的事件。

事件化模型为我们带来了一种将应用状态变化转换为事件的直观方式。Backbone及其他MVC框架做的每件事都跟这些模型有关,这些模型的状态变化会触发 DOM 和服务器进行更新。要想掌控客户端JavaScript 应用程序与日俱增的复杂度,运用事件化模型存储互斥数据是伟大长征的第一步。

总结

只要对象带有PubSub接口,就可以称之为事件化对象。特殊情况出现在用于存储数据的对象因内容变化而发布事件时,这里用于存储数据的对象又称作模型。模型就是MVC(Model-View-Controller,模型视图控制器)中的那个M。MVC三层架构设计模式在最近几年里已经成为JavaScript编程中最热点的主题之一。MVC的核心理念是应用程序应该以数据为中心,所以模型发生的事件会影响到 DOM(即MVC中的视图)和服务器(通过MVC中的控制器而产生影响)。


作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏️ | 留言📝

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。