开发者社区> 云栖大讲堂> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

JavaScript设计模式

简介: 设计模式的意义 模式为常见问题提供了行之有效的解决方案:模式提供了解决特定问题的优化模板; 模式旨在重用:它们具备通用性,适合于各种问题。 《设计模式:可复用的面向对象软件基础》的作者『四人帮』将设计模式宽泛地划分为以下几类: 创建型设计模式:该模式处理的是用于创建对象的各种机制,这种模式着眼于优化的或更可控的对象创建机制; 结构型设计模式:该模式考虑的是对象的组成以及对象彼此之间的关系,其意图在于将系统变化对整个对象关系所造成的影响降低到最小; 行为型设计模式:该模式关注的是对象之间的依赖关系以及通信。
+关注继续查看

设计模式的意义

  • 模式为常见问题提供了行之有效的解决方案:模式提供了解决特定问题的优化模板;
  • 模式旨在重用:它们具备通用性,适合于各种问题。

《设计模式:可复用的面向对象软件基础》的作者『四人帮』将设计模式宽泛地划分为以下几类:

  • 创建型设计模式:该模式处理的是用于创建对象的各种机制,这种模式着眼于优化的或更可控的对象创建机制;
  • 结构型设计模式:该模式考虑的是对象的组成以及对象彼此之间的关系,其意图在于将系统变化对整个对象关系所造成的影响降低到最小;
  • 行为型设计模式:该模式关注的是对象之间的依赖关系以及通信。

这几类设计模式又有更详细的分类:

创建型模式:

  • 工厂方法
  • 抽象工厂
  • 建造者
  • 原型
  • 单例

结构型模式:

  • 适配器
  • 桥接
  • 组合
  • 装饰器
  • 外观
  • 享元
  • 代理

行为型模式:

  • 解释器
  • 模板方法
  • 责任链
  • 命令
  • 迭代器
  • 中介者
  • 备忘录
  • 观察者
  • 状态
  • 策略
  • 访问者

而针对于 JavaScript,则有更为特殊的设计模式。

命名空间模式

命名空间能够减少程序创建的全局变量的数量,有助于避免命名冲突或过多的名称前缀。

命名空间的思路是为应用程序或库创建一个全局对象,将所有的其他对象和函数全部添加到该对象中去,而不是去污染全局作用域。

我们可以创建单个全局对象,然后将所有的函数和对象作为该全局对象的一部分:

var FACTORY = FACTORY || {};
FACTORY.Car = function () {};
FACTORY.Bike = function () {};
FACTORY.engines = 1;

全局命名空间对象名习惯上全部都是大写。

命名空间模式既可以限制全局变量,又能够为代码添加命名空间,看起来似乎是一个不错的方法,但多少有点繁琐,你得在每个变量和函数前面加上命名空间前缀,需要输入更多的内容,代码也变得啰嗦。另外,单个全局实例意味着任何代码都能够修改该全局实例,进而影响到其他功能。

模块模式

模块模式有助于维持代码清晰的独立性以及条理性。

模块可以将较大的程序分割成较小的部分,赋予其各自的命名空间。这一点非常重要,因为一旦将代码划分成模块,这些模块就能够在其他地方重新使用。精心设计的模块接口能够使代码易于重用和扩展。

JavaScript 提供了灵活的函数和对象,可以轻松地创建出健壮的模块系统。函数作用域有助于创建模块内部使用的命名空间,对象可以用来保存导出的值。

模块可以模拟类的概念,它使得我们能够在对象中加入公共/私有方法和变量,但最重要的是,模块能够将它们同全局作用域隔离开。变量和函数都被限制在了模块作用域中,因而也就自动避免了与使用相同名称的其他脚本产生命名冲突。

模块模式的另一个有点在于它只暴露了公共 API,其他所有与内部实现相关的细节都以私有状态保留在模块的闭包中。

以下是一种模块模式的实现方法:

var revealingExample = function() {
    var privateOne = 1;
    function privateFn() {
        console.log('privateFn called');
    }
    var publicTwo = 2;
    function publicFn() {
        publicFnTwo();
    }
    function publicFnTwo() {
        privateFn();
    }
    function getCurrentState() {
        return 2;
    }
    // 通过分配共有指针来暴露私有变量
    return {
        setup: publicFn,
        count: publicTwo,
        increaseCount: publicFnTwo,
        current: getCurrentState()
    };
}();
console.log(revealingExample.current); //2
revealingExample.setup(); //调用 privateFn

ES6 模块

ES6 模块的语法类似于 CommonJS,另外还支持异步装载以及可配置的模块装载:

// json_processor.js
function processJSON(url) {
    ...
}
export function getSiteContent(url) {
    return processJSON(url);
}
// main.js
import { getSiteContent } from "json_processor.js";
content = getSiteContent("http://google.com/")

ES6 的导出功能可以让你使用类似于 CommonJS 的方式导出函数或变量。

工厂模式

工厂模式是另一种流行的对象创建模式。该模式提供了一个用于创建对象的接口。根据传入工厂的类型,可以创建出特定类型的对象。这种模式常见的实现通常是利用类或类的静态方法。这样的类或方法的目的如下:

  • 在创建相似对象时,抽象出重复出现的操作;
  • 允许工厂的用户在无需了解对象创建的内部细节的情况下创建对象;

让我们用一个常见的例子来理解工厂模式的用法。假设我们有:

  • 一个构造函数 CarFactory()
  • CarFactory 中一个叫做 make() 的静态方法,该方法知道如何创建 car 类型的对象
  • 特定的 car 类型,例如 CarFactory.SUVCarFactory.Sedan 等

我们希望像下面这样使用 CarFactory:

var golf = CarFactory.make('Compact');
var vento = CarFactory.make('Sedan');
var touareg = CarFactory.make('SUV');

这里给出了这种工厂的实现方法。下面的实现非常标准。我们用编程的方式调用了构造函数,创建指定类型的对象——CarFactory[const].prototype = new CarFactory();

我们将对象类型映射为构造函数。该模式的实现方法不止一种:

// 工厂构造函数
function CarFactory() {}
CarFactory.prototype.info = function() {
    console.log("This car has " + this.doors + " doors and a " + this.engine_capacity + " liter engine");
};
// 静态工厂方法
CarFactory.make = function (type) {
    var constr = type;
    var car;
    CarFactory[constr].prototype = new CarFactory();
    // 创建新的实例
    car = new CarFactory[constr]();
    return car;
};
CarFactory.Compact = function () {
    this.doors = 4;
    this.engine_capacity = 2;
};
CarFactory.Sedan = function () {
    this.door = 2;
    this.engine_capacity = 2;
}
CarFactory.SUV = function () {
    this.door = 4;
    this.engine_capacity = 6;
}
var golf = CarFactory.make('Compact');
var vento = CarFactory.make('Sedan');
var touareg = CarFactory.make('SUV');
golf.info(); // "This car has 4 door and a 2 liter engine"

mixin 模式

mixin 模式能够显著减少代码中重复出现的功能,有助于功能重用。我们可以将能够共享的功能放到 mixin 中,以此降低共享行为的重复数量。

考虑下面的例子,我们想创建一个定制的日志记录器(logger),任何对象实例都可以使用,这个日志记录器会在希望使用/扩展 mixin 对象之间共享:

var _ = require('underscore');
// 将共享的功能封装进 CustomLogger
var logger = (function () {
    var CustomLogger = {
        log: function (message) {
            console.log(message);
        }
    };
    return CustomLogger
}())
// 需要定制的日志记录器来记录系统特定日志的对象
var Server = (function (Logger) {
    var CustomServer = function () {
        this.init = function () {
            this.log("Initializing Server...");
        };
    };
    
    // 将 CustomLogger 的成员复制/扩展为 CustomServer
    _.extend(CustomServer.prototype, Logger);
    return CustomServer;
}(logger));
(new Server()).init(); //初始化服务器

我们动态地将 mixin 的功能加入到了对象中,重要的是理解 mixin 和继承之间的区别。如果有可以在多个对象和类层次之间共享的功能,可以使用 mixin;如果要共享的功能是在单个层次中,可以使用继承。在原型继承中,如果继承来自原型,那么对原型做出的修改会影响到从原型继承的一切内容。如果不希望出现这种情况,可以使用 mixin。

观察者模式

在《设计模式》一书中,是这样定义观察者模式的:

对目标状态感兴趣的一个或多个观察者,通过将自身与该目标关联在一起的形式进行注册。当目标出现观察者可能感兴趣的变化时,发出提醒消息,进而调用每个观察者的更新方法。如果观察者对目标状态不再感兴趣,只需要解除关联即可。

在观察者模式中,目标保存了一个对其依赖的对象列表(称为观察者),并在自身状态发生变化时通知这些观察者。目标所采用的通知方式是广播。观察者如果不想再被提醒,可以把自己从列表中移除。我们可以对该模式中的参与者做出如下定义:

  • 目标:保存观察者列表,拥有可以添加、删除和更新观察者的方法;
  • 观察者:为那些需要在目标状态发生变化时得到提醒的对象提供接口。

让我们来创建一个能够添加、删除和提醒观察者的目标:

var Subject = (function () {
    function Subject () {
        this.observer_list = [];
    }
    // 该方法用于向内部列表中添加观察者
    Subject.prototype.add_observer = function (obj) {
        console.log('Added observer');
        this.observer_list.push(obj);
    };
    
    Subject.prototype.remove_observer = function (obj) {
        for(var i = 0; i < this.observer_list.length; i++) {
            if(this.observer_list[i] === obj) {
                this.observer_list.splice(i, 1);
                console.log('Removed Observer');
            }
        }
    };
    
    Subject.prototype.notify = function () {
        var args = Array.prototype.slice.call(arguments, 0);
        for(var i = 0; i < this.observer_list.length; i++) {
            this.observer_list[i].update(args)
        }
    };
    
    return Subject
})();

Subject 的实现方法非常直观,notify() 方法的重要之处在于调用所有观察者对象的 update()方法来广播更新。

现在来定义一个能够创建随机推文的简单对象,该对象提供了一个接口,可以使用 addObserver()和 removeObserver() 方法来添加和删除观察者,它也可以使用新获取的推文来调用 Subject 的 notify() 方法。如果出现这种情况,所有的观察者都会将新发布的推文作为参数,发出有推文更新的广播:

function Tweeter () {
    var subject = new Subject();
    this.addObserver = function (observer) {
        subject.add_observer(observer);
    };
    this.removeObserver = function (observer) {
        subject.remove_observer(observer);
    };
    this.fetchTweets = function fetchTweets () {
        // tweet
        var tweet = {
            tweet: "This is one nice Observer"
        };
        // 提示观察者发生的变化
        subject.notify(tweet);
    };
}

添加两名观察者:

var TweetUpdater = {
    update: function () {
        console.log('Update Tweet - ', arguments);
    }
};
var TweetFollower = {
    update: function () {
        console.log('Following this tweet - ', arguments);
    }
};

两名观察者都有 update() 方法,该方法将由 Subject.notify() 调用。现在我们就可以通过 Tweeter 的接口将观察者添加到 Subject 中了:

var tweetApp = new Tweeter();
tweetApp.addObserver(TweeterUpdater);
tweetApp.addObserver(TweeterFollower);
tweetApp.fetchTweets();
tweetApp.removeObserver(TweetUpdater);
tweetApp.removeObserver(TweetFollower);

小结

在大型应用的构建中,我们发现某些问题模式会反复地出现,这类问题都有明确的应对方法,可以拿来重用以构建一套健壮的解决方案,这便是应用设计模式的意义所在。

原文发布时间为:2018年6月9日

原文作者:alloween.top

本文来源:掘金如需转载请联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
JavaScript中的设计模式-策略模式
设计模式在我们编程中是十分重要的! 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
19 0
深入理解JavaScript系列(33):设计模式之策略模式(转)
介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很多时候都是按照swith语句来判断,但是这就带来几个问题,首先如果增加需求的话,我们还要再次修...
717 0
jquery-barcode:js实现的条码打印
这是一个纯js的jQuery插件,项目地址:http://barcode-coder.com/en/barcode-jquery-plugin-201.html 使用示例: 1 doctype html> 2 3 4 jQuery Barcode ...
1185 0
js模仿java的Map集合,实现功能
<p style=""><span style="">java.util 中的集合类包含 Java 中某些最常用的类。最常用的集合类是 List 和 Map。List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象元素列表。List 适用于按数值索引访问元素的情形。</span></p> <p style=""><span
1561 0
100行JS实现HTML5的3D贪吃蛇游戏
js1k.com收集了小于1k的javascript小例子,里面有很多很炫很酷的游戏和特效,今年规则又增加了新花样,传统的classic类型基础上又增加了WebGL类型,以及允许增加到2K的++类型,多次想尝试提交个小游戏但总无法写出让自己满意还能控制在这么小的字节范围。
1071 0
js实现按回车自行提交
document.onkeydown = function (e) { var theEvent = window.event || e; var code = theEvent.
533 0
node-webkit无边框窗口用纯JS实现拖动改变大小
                                    $(function () {             var gui = require('nw.
790 0
js实现图片上传预览及进度条
原文js实现图片上传预览及进度条     最近在做图片上传的时候,由于产品设计的比较fashion,上网找了比较久还没有现成的,因此自己做了一个,实现的功能如下:      1:去除浏览器默认的样式;      2:图片从本地选择后,立即预览图片;      3:使用上传可以查看上传进度(本...
1450 0
js实现页面跳转的几种方式
第一种:     &lt;script language="javascript" type="text/javascript"&gt;            window.location.href="login.jsp?backurl="+window.location.href;     &lt;/script&gt; 第二种:     &lt;script language="
1258 0
+关注
云栖大讲堂
擅长前端领域,欢迎各位热爱前端的朋友加入我们( 钉钉群号:23351485)关注【前端那些事儿】云栖号,更多好文持续更新中!
3913
文章
1754
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载