常用的Javascript设计模式小结

网络编程 2025-04-04 15:49www.168986.cn编程入门

JavaScript作为一门灵活且强大的语言,拥有众多的设计模式。本文将为您介绍一些常用的JavaScript设计模式,希望能对广大JavaScript爱好者有所帮助。

正如《Practical Common Lisp》的作者Peter Seibel所言,当我们频繁使用某种设计模式时,往往是因为语言的某些局限性所致。各种编程语言都有其自身的优缺点,正如不同领域的运动员有其擅长的项目。在编程世界中,不同的语言特性和环境决定了我们采用何种设计模式。

在JavaScript中,设计模式的应用更为灵活多变。正如某些静态语言可能需要复杂的装饰者模式来实现某些功能,而JavaScript由于其动态性,可以随时向对象添加方法,使得某些设计模式变得相对简单。但这并不意味着JavaScript没有独特的设计模式。

我们来谈谈单例模式。单例模式的目标是确保一个类只有一个实例,并提供一个全局访问点。在JavaScript中,由于其“无类”的特性,实现单例模式的方式多种多样。

举一个常见的例子,我们在网页中经常需要点击某个按钮来弹出遮罩层。为了实现这个功能,我们可以创建一个遮罩层的单例,确保在全文中只有一个这样的遮罩层实例。这样,无论用户在何处点击按钮,都会调用同一个遮罩层实例,避免了重复创建和内存浪费。

除了单例模式,JavaScript中还有许多其他常用的设计模式,如工厂模式、观察者模式、模块模式等。每种模式都有其独特的应用场景和优势。

遗憾的是,目前关于JavaScript设计模式的书籍相对较少,《Pro JavaScript Design Patterns》是其中较为经典的一本。但由于其例子较为繁琐,结合工作中的实际经验,我们可以更简洁、更直观地理解这些设计模式。

我的理解可能存在偏差,欢迎各位读者不吝指正。希望大家能对JavaScript设计模式有更深入的了解,并在实际开发中灵活应用。

JavaScript中的单例模式:遮罩层的创建与优化

在web开发中,我们经常需要创建遮罩层,例如在登录界面或者弹出窗口等场景。实现遮罩层的代码看似简单,但如何优化以确保其全局唯一性且资源利用高效,则需要深入。本文将带您走进JavaScript中的单例模式,了解如何创建一个全局唯一的遮罩层,并对其进行优化。

我们来看最初的代码实现。创建一个全局的遮罩层div,每次点击按钮时显示它。但这种方法存在一个问题:每次调用createMask都会创建一个新的div,即使隐藏后也无法避免资源的浪费。这显然不是一个合理的解决方案。

接下来,我们尝试在页面一开始就创建这个遮罩层div,并用一个变量来引用它。这样确实确保了只有一个遮罩层div,但如果永远不需要这个遮罩层,就会浪费一个div。我们需要更加高效的方法。

于是,我们想到了单例模式。单例模式确保一个类只有一个实例,并提供一个全局访问点。在JavaScript中,我们可以借助闭包来实现单例模式。通过创建一个函数,该函数返回一个对象或变量,利用JavaScript的作用域规则确保该对象或变量的唯一性。

为了实现这个模式,我们首先需要创建一个函数createMask,它检查是否已经存在一个遮罩层div。如果存在,就直接返回;如果不存在,就创建一个新的div并返回。为了实现这个逻辑,我们利用了一个闭包来封装一个局部变量mask。这样,即使在多人协作的项目中,也能确保createMask函数的封闭性和安全性。

这种实现方式仍然存在问题。我们需要一个全局变量来保存遮罩层的引用。为了解决这个问题,我们进一步改进了createMask函数,使用了一个通用的singleton包装器函数。这个包装器接受一个函数作为参数,并返回一个新的函数。这个新的函数会检查是否已经调用了传入的函数并返回结果,如果是第一次调用,则执行传入的函数并保存结果。通过这种方式,我们可以避免全局变量的使用,并提高代码的复用性。

文章标题:工厂模式的魅力:简单实例与观察者模式

工厂模式犹如那神奇的饮料机,只要轻轻一按按钮,就可以轻松制作出您心仪的饮料。不论是咖啡还是牛奶,完全取决于您的选择。同样,在创建ajax对象时,简单工厂模式也发挥了巨大的作用。

近期,我开发了一个名为DanceRequest的库,专门处理ajax异步嵌套问题。这个库在GitHub上的地址是:[

使用Request这个工厂方法时,它会根据后续的代码决定究竟是产生xhr的实例还是jsonp的实例。实际上在JavaScript中,所谓的构造函数也是一种简单工厂模式,只是披上了一层new的外衣。让我们揭开这层外衣看看里面的内容。

接下来,让我们转向观察者模式。观察者模式(也被称为发布者-订阅者模式)是最常用的模式之一,在许多编程语言中都得到了广泛应用。在JavaScript和DOM之间也实现了观察者模式。例如,我们可以订阅一个div元素的click事件,当点击这个div时,预先定义的函数就会被触发。

观察者模式在生活中也有广泛的应用。比如面试场景中的“请留下你的联系方式,有消息我们会通知你”,这里的面试官是发布者,“我”是订阅者。观察者模式实现了两个模块之间的解耦。比如在一个团队开发html5游戏时,当游戏开始需要加载图片素材时,观察者模式就能发挥巨大的作用。游戏开发者可以订阅一个事件来监听图片加载的状态,一旦图片加载完成,就可以触发相应的处理函数,而不需要不断轮询图片加载的进度。这样可以使游戏更加流畅和稳定。

在多人合作的项目中,我完成了Gamer和Map模块,而我的同事A则编写了一个图片加载器loadImage。当图片加载完毕后,程序会渲染地图并执行游戏逻辑。这个流程运行得很好。有一天我意识到应该给游戏添加声音功能。为了实现这一点,我需要在图片加载器中增加一行代码。考虑到同事A在外地旅游,我联系了他,询问能否修改loadImage函数,并了解是否有潜在的副作用。经过沟通和理解后,我决定使用观察者模式进行重构。

观察者模式是一种事件驱动的设计模式,允许对象之间解耦并异步通信。在loadImage的实现中,我们可以使用观察者模式来监听图片加载的完成事件。这样,当图片加载完成后,任何监听了该事件的对象都会收到通知。这就像面试官将面试者的简历收集起来,并在面试结果出炉后通知他们一样。我们可以把事件名称命名为'ready',以便于理解事件的用途。下面是一个具体的实现示例:

我们创建一个名为Events的函数来管理事件的监听和触发:

```javascript

Events = function() {

var listen, log, obj, one, remove, trigger;

obj = {}; // 用于存储事件和对应的回调函数集合

listen = function(key, eventfn) { // 添加监听事件的方法

var stack; // 存储回调函数的数组

stack = obj[key] || []; // 如果当前事件没有对应的回调函数数组则创建一个新的数组

stack.push(eventfn); // 将回调函数添加到数组中

}; // 当某个事件触发时调用所有注册在该事件上的回调函数集合

trigger = function() { // 触发事件的方法

var key, stack; // 事件名称和对应的回调函数数组

key = Array.prototype.shift.call(arguments); // 获取事件名称作为参数

stack = obj[key] || []; // 获取该事件对应的回调函数数组(如果没有则创建一个空数组)

for (var i = 0, len = stack.length; i < len; i++) { // 遍历回调函数数组并执行回调函数

stack[i].apply(null, Array.prototype.slice.call(arguments)); // 执行回调函数并传递参数给函数本身(这里使用了apply方法)

理解代理模式的价值,或许能让我们换个角度看世界。若以追求MM为例,赠送一台宝马虽能迅速俘获芳心,但代理模式的妙处却在于细致入微的关怀。

设想一下,每日需提交工作日报的场景。如果每位员工都直接将报告发送给总监,那么总监的办公桌上恐怕会堆积如山。这时,代理模式便派上了用场。我们先把日报发给组长,组长作为代理,将一周的报告汇总后,再交给总监。如此,既确保了工作的有序进行,又让总监得以从容审阅。

在编程世界里,代理模式同样大有作为。当面临性能问题时,它的应用尤为广泛。例如,在频繁的访问dom节点或请求远程资源时,我们可以先将操作存入缓冲区,选择最佳的时机触发。这种模式的运用,如同游戏中的技能释放,需要合适的时机才能发挥出最大威力。

让我们以街头霸王游戏为例。在游戏中,隆需要响应键盘事件以完成动作。为此,我们有一个keyManage类,它在游戏主线程中监听键盘事件。原本,它只能捕捉到单独的按键事件,却无法识别组合键。这意味着,当隆施展升龙拳(前下前+拳)时,我们仅能捕捉到单个按键的信息,而无法得知完整的动作序列。

为了解决这个问题,我们决定对keyManage类进行改进,让它支持传递按键组合。我们引入了一个代理对象来处理这些事件。这样,隆接收到的动作指令是通过代理对象处理后的结果。这种设计使得keyManage类更加灵活,可以适应不同的游戏需求。

在Ajax请求中,代理模式同样有所应用。当我们调用Ajax请求时,会给xhr对象设置一个代理。这样做的好处是,我们不必频繁地操作xhr对象发送请求,而是通过代理来管理请求。

桥接模式也是值得关注的一种设计模式。它将实现部分和抽象部分分离,以便两者能够独立变化。在实现api时,桥接模式特别有用。例如,在一个创建单例的示例中,单例的创建过程是抽象部分,而具体的实现(如创建遮罩层)则是实现部分。他们可以通过桥接模式相互独立变化,互不影响。

代理模式在编程中的应用场景广泛且实用。它如同一座桥梁,连接着不同的部分,使得整体系统更加灵活、高效。理解了代理模式的价值,我们在追求更高目标时,也能更加得心应手。关于创建脚本的简易性和forEach函数的实现

对于编程者来说,创建一个简单的脚本应该是一种轻松自如的体验。当我们谈到JavaScript中的`createScript`时,我们可以想象一个简单的方法,只需调用一次函数就能完成脚本的创建和附加。例如:

```javascript

var createScript = function(){

return document.body.appendChild(document.createElement('script'));

};

```

使用`singleton`模式确保此功能在整个应用中只被实例化一次,从而优化性能。如果你需要再次创建脚本,就不会费力,因为这个操作已经变得非常简便。

forEach函数的直观展示

想象一下你有一个数组,你想对数组中的每个元素执行某些操作。这时,`forEach`函数就派上了用场。它的实现并不复杂,但却非常实用。例如:

```javascript

function forEach(ary, fn){

for (var i = 0, l = ary.length; i < l; i++){

var c = ary[i];

if (fn.call(c, i, c) === false){ // 这里调用fn函数并传递当前元素和索引作为参数

return false; // 如果fn函数返回false则提前退出循环

}

}

}

```

```javascript

forEach([1, 2, 3], function(i, n){ // 对数组中的每个元素执行一个弹出警告框的操作

alert('当前元素是 ' + n); // 注意这里使用n而不是可能的参数名c或i,以保持一致性并避免混淆。但这仅仅是示例代码,实际开发中应该避免使用全局变量。

});

```

在上面的代码中,你可以看到我们只是简单地遍历数组并弹出一个警告框来显示当前元素的值。无论你的函数内部逻辑如何复杂,`forEach`都能确保每个元素都被正确处理。我们还可以利用它来处理异步操作或进行更复杂的逻辑处理。 无需担心`forEach`函数会干扰我们的代码逻辑。这是一个强大的工具,让你可以轻松地对数组进行操作。 无需担心内部实现细节,只需关注你的业务逻辑即可。 这就是编程的简洁性和高效性所在。 无需过度复杂化代码结构或编写冗余的代码片段来执行简单的任务。 使用这些基本工具可以大大提高开发效率和代码质量。同时我们也可以理解一些概念在实际中的应用情况和使用场景从而加深对它的理解与应用。所以在此基础上让我们再深入了解一些其他的模式或者概念比如外观模式和访问者模式。 关于外观模式通俗一点讲就是将一系列复杂的操作封装成一个简单的接口供用户使用用户只需要关注这个接口而无需关心内部具体的实现细节比如你去饭店吃饭饭店提供的菜单就是外观你只需点你想要的菜品厨师就会为你做好至于厨师如何烹饪你无需关心这就是外观模式的魅力所在它提高了系统的易用性降低了系统的复杂性。再举一个例子我们常见的事件处理在事件处理过程中往往会涉及到事件的默认行为和冒泡的处理我们可以将这些处理封装成一个外观模式使得使用者无需关心这些细节直接使用封装好的方法即可。关于访问者模式通俗点讲访问者模式是一种将某些行为从对象中抽离出来单独形成一个访问者对象这些对象可以通过调用访问者对象的方法来执行某些操作这样设计的好处在于增加了代码的模块化提高了代码的复用性同时使得对象结构更加清晰易于维护。总的来说访问者模式是一种将复杂的行为抽象出来单独处理的设计思想通过访问者对象将不同的对象进行访问和操作使得代码更加清晰易于理解和维护。在JavaScript这种基于鸭子类型的语言中,访问者模式几乎是一种原生实现。利用apply和call,我们可以轻松地使用访问者模式。让我们深入了解这种模式的思想及其在JavaScript引擎中的实现。

我们来谈谈什么是鸭子类型。让我们讲个故事。很久以前,有一个皇帝喜欢听鸭子呱呱叫。他召集大臣组建了一个一千只鸭子的合唱团。大臣们从全国各地抓来了鸭子,但始终差一只。这时,一只自告奋勇的鸡表示它也会呱呱叫。皇帝听了后,并不在意它是鸭子还是鸡,只要会呱呱叫就行。这就是鸭子类型的概念。在JavaScript这种弱类型语言中,许多方法并不进行对象的类型检测,而是关注这些对象能做什么。例如,Array构造器和String构造器的prototype上的方法就被设计成了访问者。这些方法不对this的数据类型进行任何校验,这也是arguments能冒充array调用push方法的原因。

在V8引擎中,Array.prototype.push的代码并没有对this的类型进行显式限制。理论上任何对象都可以作为访问者传入ArrayPush这个方法。在实际执行过程中,仍然存在一些隐式限制。例如,this对象必须有可存储属性的空间,以及this的length属性必须是可写的。如果不符合这些规则,代码会在执行期间报错。

利用访问者模式,我们可以做一些有趣的事情,比如给一个object对象增加push方法。下面是一个示例:

```javascript

var Visitor = {};

Visitor.push = function() {

return Array.prototype.push.apply(this, arguments);

};

var obj = {};

obj.push = Visitor.push;

obj.push('"first"');

alert(obj[0]); // "first"

alert(obj.length); // 1

```

接下来,我们谈谈策略模式。策略模式的目的是定义一系列算法,将它们封装起来,并使其可以相互替换。在jQuery的animate方法中,linear(匀速)和cubic(三次方缓动)就是策略模式的封装。再举一个例子,在我之前写的dev.qplus.中,很多页面都有即时验证的表单。每个表单元素都有一些不同的验证规则,如非空、敏感词、字符过长等。虽然可以通过写多个if else来解决这个问题,但当表单元素和验证规则增多时,代码会变得难以维护。更好的做法是将每种验证规则用策略模式单独封装,需要哪种验证时只需提供对应的策略名称即可。这种做法提高了代码的扩展性和可维护性。

在编程的世界里,验证规则的存在是为了确保数据的准确性和系统的稳定性。想象一下,我们有一个名为`nameInput`的输入字段,它需要进行一系列的验证以确保用户输入的有效性。其中,`notNull`和`maxLength`是两种常见的验证规则。这些规则通过简单的函数实现,如`notNull`验证输入值是否非空,而`maxLength`则检查输入长度是否超过预设的最大限制。这些函数的存在,使得验证规则变得灵活且易于修改。

想象一下,如果产品经理突然要求将字符长度的限制从30个字符改为60个字符,这时,我们的代码会如何应对?只需稍作调整,瞬间完成。这种灵活性正是我们追求的最佳体验。

再来看一个有趣的概念——“模版方法模式”。这种模式允许我们预先定义一组算法的基本框架,然后在子类中实现具体的步骤。这就像是一个建造房屋的蓝图,主体结构已经确定,但具体的装饰和细节可以根据需求进行调整。这种模式在编程中非常常见,特别是在大型项目中,架构师会提供一个基本的框架,然后让开发者根据需求进行填充和扩展。

进一步来说,如果我们从更高的角度来看待这个问题,会发现这种模式的存在就像上帝在创造生命时所遵循的法则。假设上帝在创造生命时使用了编程的方式,那么生命的构建过程就像是一个预先设定的模版方法。生命的各个阶段(DNA复制、出生、成长、衰老和死亡)都是固定的步骤,而每个物种在这个过程中都有自己独特的实现方式。这就像是在一个大型项目中,架构师提供了一个基本的框架,然后各个开发者团队在这个框架下进行具体的实现。

回到现实世界,以一个游戏大厅中的游戏为例。所有的游戏都有相似的生命周期:登录、游戏过程、游戏结束等阶段。而登录、游戏结束后的提示等过程都是公用的。这时,我们可以使用模版方法模式来定义这些公共步骤,然后让各个游戏根据自身的特点来实现具体的逻辑。这样,既保证了游戏的共性,又让每个游戏都有其独特的魅力。

游戏中心的构建与新游戏的诞生

设想一个游戏中心的功能框架,我们将其命名为gameCenter。这个框架包含初始化、登录、游戏开始和结束的四个基本步骤。其中,游戏开始部分是一个空函数,等待子类去具体实现。

接下来,我们创建一个新的游戏——,只需要继承gameCenter并填充其gameStart函数即可。如此,当我们调用这个新游戏的init函数时,一局新的游戏便开始了。

再来说说中介者模式。在软件设计中,中介者模式是一种让各个对象之间解耦,降低它们之间的直接依赖关系,同时保证它们能够独立改变交互方式的策略。这就像军火交易中的中介者,让买家和卖家在不直接交流的情况下完成交易。中介者的存在使得交易更加安全、灵活。在银行系统中,存款人和贷款人的交易也是如此,因为有中介的存在,这场交易才变得如此方便。中介者模式和代理模式虽然都涉及到第三方对象参与两个对象的通信,但它们之间存在一些差异。代理模式中,代理对象必须了解委托对象的所有细节;而在中介者模式中,中介者并不关心各个对象的具体实现细节,它只负责协调和管理对象间的交互。

在程序世界里,很多框架都运用了中介者模式的思想。比如MVC架构中的控制器就扮演了中介者的角色。在Backbone.js等JS框架中,Controller作为中介者,将Model和View之间的复杂关系简化。

让我们简单了解一下迭代器模式。迭代器模式允许我们顺序访问聚合对象中的各个元素,而无需了解这些元素的内部表示。在JavaScript中,我们经常封装一个each函数来实现迭代器,遍历数组或对象中的每一个元素。

这就是关于游戏中心构建、新游戏的开发、中介者模式和迭代器模式的基本介绍。希望这些内容能激发您对软件设计和开发的兴趣!一、数组的迭代器

对于数组,我们经常会使用forEach方法来遍历每一个元素并执行特定的操作。以下是使用forEach迭代器的简单示例:

```javascript

function forEachArray(array, callback) {

for (let i = 0; i < array.length; i++) {

const element = array[i];

if (callback(element, i) === false) {

return false;

}

}

}

forEachArray([1, 2, 3], function(index, value) {

alert(value);

});

```

二、对象的迭代器

对象也可以使用类似的迭代器进行遍历。以下是使用forEach迭代器遍历对象的简单示例:

```javascript

function forEachObject(obj, callback) {

for (let key in obj) {

const value = obj[key];

if (callback(key, value) === false) {

return false;

}

}

}

forEachObject({ a: 1, b: 2 }, function(key, value) {

alert(`Key: ${key}, Value: ${value}`);

});

```

三、组合模式(Composite Pattern)

组合模式是一种将对象组合成树形结构,使用户可以像操作单一对象一样操作组合对象及成员。在前端开发中,这种模式经常用于DOM操作和库函数的设计。例如,在jQuery中,我们可以对整个DOM树的节点进行批量操作,而无需逐一处理每个节点。这在处理大量DOM元素时非常有用。再比如,一个表单验证功能,我们可以为每个表单字段提供验证功能,然后通过一个统一的函数对所有字段进行验证。这种方式使得代码更加简洁且易于维护。伪代码如下:

四、备忘录模式(Memento Pattern)

备忘录模式主要用于保存和恢复数据状态。在JavaScript中,这种模式经常用于实现数据缓存或状态管理。例如,分页控件可以从服务器获取数据并保存在备忘录中。当用户再次访问该页时,可以直接从备忘录中获取数据,无需向服务器发送请求。这种模式有助于减少服务器压力,提高用户体验。备忘录模式也常用于实现撤销和重做功能。实现更简单,伪代码解读及状态模式

对于提供的伪代码,可以解读为以下的内容:

一个简单的页面缓存实现。当请求某一页面时,首先检查缓存中是否存在该页面的数据,如果存在则渲染缓存数据;如果不存在则通过Ajax请求获取数据并存入缓存,然后渲染。这是一种典型的缓存策略,用于提高页面加载速度和性能。

接下来是对于职责链模式的解读。职责链模式是一种处理请求或责任的传递模式。当一个对象无法处理某个请求时,它会将请求传递给另一个对象,直到有某个对象处理该请求为止。就像一个任务在团队中的传递,每个人都有自己的职责范围,当任务无法完成时,会传递给下一个队员。在Web开发中,事件冒泡机制就是一个典型的职责链模式。

然后是享元模式,也叫共享元素模式。其主要目的是减少系统中对象的数量,通过共享一些重复使用的对象来节省资源。例如,在Web应用中,如果有大量的相似对象需要创建和销毁(如好友列表中的div元素),就可以使用享元模式来复用这些对象,而不是每次都创建新的。这可以提高性能并减少内存占用。伪代码中的getDiv函数展示了这种模式的实现方式。

最后是状态模式。当一个对象的行为根据其内部状态的不同而有所不就可以使用状态模式。例如,在游戏角色(如街头霸王中的隆)中,角色有多种状态(走动、攻击、防御、跌倒、跳跃等),每种状态下其行为都会有所不同。状态模式可以帮助我们更好地管理和处理这些状态及其转换。

在编程的世界中,逻辑的繁复往往隐藏着无尽的if-else分支。设想一下,每当人物状态变化时,我们必须逐一考虑攻击、防御、跳跃等动作的限制。这往往导致代码从第10行蔓延至数百行,难以维护。当我们引入状态类后,这一切变得井井有条。

想象一下我们的角色在战场上跃起、等待、攻击与防御的动态画面。每一次动作的变化都与状态紧密相连。状态的变化并不只是一个简单的判断,它背后隐藏着丰富的逻辑与规则。而状态类,正是将这些规则集中管理的好工具。当我们要判断角色是否能攻击或防御时,我们不再需要在代码中进行漫长的搜索。只需检查当前的状态,然后进行相应的操作即可。而所有的状态转换逻辑,都被封装在状态类中。这不仅使得代码更加简洁明了,还便于维护和修改。例如,当我们需要添加一个新的状态时,只需在状态类中增加相应的函数即可,无需对现有代码进行大规模的修改。而调用者只需通过暴露的接口来切换人物的状态,无需关心背后的复杂逻辑。这如同为编程世界注入了清新之风,让代码变得更加灵动与高效。正如功夫中的招式,招式本身是固定的,但如何运用则要看实际情况和需求。在设计模式的学习中,不必刻意追求固定的模式。在实际编码中,也应根据实际需求来选择合适的设计模式或策略。如同中的姿势选择一样,关键在于感觉和需求,而非刻意为之。通过引入状态类,我们可以更好地管理复杂的逻辑分支,使代码更加清晰、易于维护。这不仅提高了编程的效率,也使得代码更加易于理解和学习。让我们在编程的道路上继续前行,更多的可能性!我在实践中还注意到了其他设计模式在实际编码中的实际应用场景,比如在读取大量数据和逻辑判断时使用的观察者模式等。这些设计模式的运用使得代码更加灵活和高效。同时我也明白了在实践中不断学习的重要性和不断提升自己编码技能的重要性。只有这样,我们才能在编程的道路上不断前行并取得更大的成功!

上一篇:Yii数据读取与跳转参数传递用法实例分析 下一篇:没有了

Copyright © 2016-2025 www.168986.cn 狼蚁网络 版权所有 Power by