JavaScript的学习--JavaScript设计模式的总结
这篇博客只是自己对设计模式的理解的备忘~
看完了《JavaScript设计模式》这本书,一直没有写博客记录一下,最近抽出时间来重读了一下,就顺便记录一下~
如果你只是想粗略了解一下JavaScript的设计模式,继续读下去,就好了,如果你想详细了解的话,推荐汤姆大叔的系列博客 深入理解JavaScript系列
下面有些内容也是摘自汤姆大叔的博客~~
1.Constructor(构造器)模式
声明一个首字母大写的function,通过在构造器前面加new关键字,实例化一个对象。
可以使用原型来定义函数,原型定义的函数,不同的对象可以共用。
例子
// Constructor pattern function Car(modal, year, miles) { this.modal = modal; this.year = year; this.miles = miles; // 这个方法会覆盖prototype中的toString方法 this.toString = function() { return this.modal + " in object toString function"; } } Car.prototype.toString = function() { return this.modal + " in prototype toString function"; }; var a = new Car("a", 2009, 20000); var b = new Car("b", 2011, 1000); console.log(a.toString()); console.log(b.toString());
2.Module(模块)模式
可以直接声明一个module对象。也可以执行一个函数,返回一个module对象,执行函数时可以引入一些变量(如JQuery、Underscore)。第二种方式可以引入私有变量和私有函数。
例子
// Module pattern // 引入了jQuery和Underscore var myModule = (function(jQ) { // 私有变量 var privateVar = "private"; // 私有函数 function privateMethod() { jQ("body").html("test"); }; return { // 公有变量 publicVar: "public", // 公有函数 publicMethod: function() { privateVar += "Var"; privateMethod(); console.log(privateVar); } }; })(jQuery); // 调用公有函数 myModule.publicMethod()
优点:对拥有面向对象背景的开发人员来说更整洁,而且它支持私有数据。
缺点:如果要修改可见性(即公有还是私有)时,必须修改每一个曾经使用过该成员的地方,之后也无法在方法里添加私有成员。
3.Revealing Module(揭示模块)模式
将公有指针指向私有的函数和属性上,个人感觉有点像Java的对象。
例子
// Revealing Module pattern var myRevealingModule = function() { var privateVar = "Harry", publicVar = "Potter"; function privateFunction() { console.log("Name : " + private); } function publicSetName(strName) { privateVar = strName; } function publicGetName() { privateFunction(); } // 将暴露的公有指针指向到私有函数和属性上 return { setName: publicSetName, getName: publicGetName, familyName: publicVar } }
优点:可以使脚本语法一致,很容易看出哪些函数和变量可以被公开访问,可读性高。
缺点:如果一个私有函数引用了一个共有函数,在将公有函数替换掉后,只是替换了公有指针的指向,私有函数还是用调用之前的函数。
4.Singleton(单例)模式
单例模式限制了类只能实例化一次。在实例不存在的时候,它会通过一个方法创建类的新实例,如果实例已经存在,它会直接返回该对象的引用。Singleton不同于静态类(或对象),可以延迟初始化。
在JavaScript中,Singleton充当共享资源命名空间,从全局命名空间中隔离出代码实现,从而为函数提供单一访问点。
例子
// Singleton pattern var mySingleton = (function() { // 实例保持了Singleton的一个引用 var instance; function init() { // Singleton // 私有函数和变量 function privateMethod() { console.log("I am private"); } var privateVar = "I am also private"; var privateRandomNumber = Math.random(); return { // 公有函数和变量 publicMethod: function() { console.log("I am public"); }, publicProperty: "I am also public", getRandomNumber: function() { return privateRandomNumber; } }; }; return { // 获取Singleton的实例,如果存在就返回,不存在就创建新实例 getInstance: function() { if(!instance) { instance = init(); } return instance; } } })(); var singleA = mySingleton.getInstance(); var singleB = mySingleton.getInstance(); console.log(singleA.getRandomNumber() === singleB.getRandomNumber());// true
模式的适用性
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
- 该唯一的实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
5.观察者(Observer)模式
观察者模式定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
例子
// Observer pattern function Observer() { this.fns = []; } Observer.prototype = { subscribe: function(fn) { this.fns.push(fn); }, unsubscribe: function(fn) { this.fns = this.fns.filter( function(el) { if (el !== fn) { return el; } } ); }, update: function(o, thisObj) { var scope = thisObj || window; this.fns.forEach( function(el) { el.call(scope, o); } ); } }; //测试 var o = new Observer; var f1 = function(data) { console.log('Robbin: ' + data + ', 赶紧干活了!'); }; var f2 = function(data) { console.log('Randall: ' + data + ', 找他加点工资去!'); }; o.subscribe(f1); o.subscribe(f2); o.update("Tom回来了!") //退订f1 o.unsubscribe(f1); //再来验证 o.update("Tom回来了!"); /* // 如果提示找不到filter或者forEach函数,可能是因为你的浏览器还不够新,暂时不支持新标准的函数,你可以使用如下方式自己定义 if (!Array.prototype.forEach) { Array.prototype.forEach = function (fn, thisObj) { var scope = thisObj || window; for (var i = 0, j = this.length; i < j; ++i) { fn.call(scope, this[i], i, this); } }; } if (!Array.prototype.filter) { Array.prototype.filter = function (fn, thisObj) { var scope = thisObj || window; var a = []; for (var i = 0, j = this.length; i < j; ++i) { if (!fn.call(scope, this[i], i, this)) { continue; } a.push(this[i]); } return a; }; } */
观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。
6.Mediator(中介者)模式
中介者是一种行为设计模式,它允许我们公开一个统一的接口,系统的不同部分可以通过该接口进行通信。如果一个系统的各个组件之间有太多直接关系,可以创建一个控制点,各个组件通过这个控制点进行通讯。简单点说,就是有个控制中心控制着各个组件之间的通讯,那个控制中心就是中介者。
高级代码可查看Mediator.js https://github.com/ajacksified/Mediator.js
例子
<!doctype html> <html lang="en"> <head> <title>JavaScript Patterns</title> <meta charset="utf-8"> </head> <body> <div id="results"></div> <script> function Player(name) { this.points = 0; this.name = name; } Player.prototype.play = function () { this.points += 1; mediator.played(); }; var scoreboard = { // 显示内容的容器 element: document.getElementById('results'), // 更新分数显示 update: function (score) { var i, msg = ''; for (i in score) { if (score.hasOwnProperty(i)) { msg += '<p><strong>' + i + '<\/strong>: '; msg += score[i]; msg += '<\/p>'; } } this.element.innerHTML = msg; } }; var mediator = { // 所有的player players: {}, // 初始化 setup: function () { var players = this.players; players.home = new Player('Home'); players.guest = new Player('Guest'); }, // play以后,更新分数 played: function () { var players = this.players, score = { Home: players.home.points, Guest: players.guest.points }; scoreboard.update(score); }, // 处理用户按键交互 keypress: function (e) { e = e || window.event; // IE if (e.which === 49) { // 数字键 "1" mediator.players.home.play(); return; } if (e.which === 48) { // 数字键 "0" mediator.players.guest.play(); return; } } }; // go! mediator.setup(); window.onkeypress = mediator.keypress; // 30秒以后结束 setTimeout(function () { window.onkeypress = null; console.log('Game over!'); }, 30000); </script> </body> </html>
中介者模式一般应用于一组对象已定义良好但是以复杂的方式进行通信的场合,一般情况下,中介者模式很容易在系统中使用,但也容易在系统里误用,当系统出现了多对多交互复杂的对象群时,先不要急于使用中介者模式,而是要思考一下是不是系统设计有问题。
另外,由于中介者模式把交互复杂性变成了中介者本身的复杂性,所以说中介者对象会比其它任何对象都复杂。
7.Prototype(原型)模式
Prototype模式为一种基于现有对象模板,通过克隆方式创建对象的模式。
可以通过Object.create创建一个拥有指定原型和对象的属性,也可以通过函数模仿构造函数创建。
例子
// Prototype pattern // 使用Object.create方法 var vehicle = { getModel: function () { console.log('车辆的模具是:' + this.model); } }; // 可以在Object.create的第二个参数里使用对象字面量传入要初始化的额外属性. // 其语法与Object.defineProperties或Object.defineProperty方法类型。 // 它允许您设定属性的特性,例如enumerable, writable 或 configurable。 var car = Object.create(vehicle, { 'id': { value: MY_GLOBAL.nextId(), enumerable: true // 默认writable:false, configurable:false }, 'model': { value: '福特', enumerable: true } }); // 模仿一个构造函数 var beget = (function(){ function F() {} return function (proto) { F.prototype = proto; } })();
8.Command(命令)模式
用于将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。也就是说改模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。此外,可以通过调用实现具体函数的对象来解耦命令对象与接收对象。
例子
// Command pattern var CarManager = { // 请求信息 requestInfo: function(model, id) { return 'The information for ' + model + ' with ID ' + id + ' is foobar'; }, // 购买汽车 buyVehicle: function(model, id) { return 'You have successfully purchased Item ' + id + ', a ' + model; }, // 组织view arrangeViewing: function(model, id) { return 'You have successfully booked a viewing of ' + model + ' ( ' + id + ' ) '; } }; // 添加执行的函数 CarManager.execute = function(name) { return CarManager[name] && CarManager[name].apply(CarManager, [].slice.call(arguments, 1)) } // 执行 CarManager.execute("arrangeViewing", "Harry Potter", "10000");
命令模式为我们提供了一种分离职责的手段,这些职责包括从执行命令的任意地方发布命令以及将该职责转而委托给不同对象。
实施明智的、简单的命令对象把action动作和调用该动作的对象绑定在一起。它们始终包括一个执行操作(如 run()或 execute())。所有具有相同接口的Command对象可以根据需要轻松交换,这被认为是该模式的一个更大的好处。
9.Facade(外观)模式
Facade模式为更大的代码提供了一个方便的高层次的接口,能够隐藏其底层的真实复杂性。可以把它想成是简化的API来展示给其他开发人员,通常是可以提高可用性的。
但该模式会影响性能,比如说在jQuery中只需要使用$()就可以取到元素,用户不需要使用$.getById()或 $.getByClass()等,但在抽象的时候(即实现的时候),就需要做处理,会降低性能。
例子
// Facade pattern var addMyEvent = function(el, ev, fn) { if(el.addEventListener) { // W3C事件模型 el.addEventListener(ev, fn, false); } else if(el.attachEvent) { // IE事件模型 el.attachEvent("on" + ev, fn); } else { // Traditional事件模型 el["on" + ev] = fn; } }
使用Facade模式时,要了解涉及的任何性能成本,确认是否值得抽象。
10.Factory(工厂)模式
Factory模式通过提供一个通用接口来创建对象。如果对象创建过程相对比较复杂,这种方法特别有用,例如,如果它强烈依赖于动态因素或应用程序配置的话。
下面是一个抽象工厂的例子
// Factory pattern // 定义Car的构造函数 function Car(options) { // 默认值 this.doors = options.doors || 4; this.state = options.state || "brand new"; this.color = options.color || "sliver"; } // 定义Truck的构造函数 function Truck(options) { this.state = options.state || "used"; this.wheelSize = options.wheelSize || "large"; this.color = options.color || "blue"; } var AbstractVehicleFactory = (function(){ // 存储车辆类型 var types = []; return { getVehicle: function( type, customizations ) { var Vehicle = types[type]; return (Vehicle) ? new Vehicle(customizations) : null; }, registerVehicle: function( type, Vehicle ) { var proto = Vehicle.prototype; // 可以加条件判断注册满足何种条件的车辆 types[type] = Vehicle; return AbstractVehicleFactory; } } })(); // 用法 AbstractVehicleFactory.registerVehicle("car", Car); AbstractVehicleFactory.registerVehicle("truck", Truck); // 基于抽象车辆类型实例化一个新的car对象 var car = AbstractVehicleFactory.getVehicle("car", { color: "lime green", state: "like new" }); // 同理实例化一个新的truck对象 var truck = AbstractVehicleFactory.getVehicle("truck", { wheelSize: "medium", color: "neon yellow" });
适用场景
- 当对象或组件设置涉及高复杂性时
- 当需要根据所在的不同环境轻松生成对象的不同实例时
- 当处理很多共享相同属性的小型对象或组件时
- 在编写只需要满足一个API契约(亦称鸭子类型)的其他对象的实例对象时。对于解耦是很有用的
11.Mixin模式
Mixin是可以轻松被一个子类或一组子类继承功能的类,目的是函数复用。
// Mixin pattern // 定义简单的Car构造函数 var Car = function(settings) { this.model = settings.model || "no modal provided"; this.color = settings.color || "no color provided"; } // Mixin var Mixin = function() {}; Mixin.prototype = { driveForword: function() { console.log("drive forword"); }, driveBackword: function() { console.log("drive backword"); }, driveSideways: function() { console.log("drive sideways"); } }; // 通过一个方法将现有对象扩展到另外一个对象上 function augment(receivingClass, givingClass) { // 只提供特定的方法 if(arguments[2]) { for (var i = 2, len = arguments.length; i < len; i++) { receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; }; } // 提供所有的方法 else { for (var methodName in givingClass.prototype) { // 确保接收类不包含所处理方法的同名方法 if(!Object.hasOwnProperty(receivingClass.prototype, methodName)) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } // 另一个方式 // if(!receivingClass.prototype[methodName]) { // receivingClass.prototype[methodName] = givingClass.prototype[methodName]; // } }; } } // 给Car构造函数增加"driveForword"和"driveBackword"两个方法 augment(Car, Mixin, "driveForword", "driveBackword"); // 创建一个新Car var myCar = new Car({ model: "Ford Escort", color: "blue" }); // 测试确保新增方法可用 myCar.driveForword(); myCar.driveBackword(); // 输出 // drive forword // drive backword // 也可以通过不声明特定方法名的形式,将Mixin的所有方法都添加到Car里 augment(Car, Mixin); var mySportCar = new Car({ model: "Porsche", color: "red" }); mySportCar.driveSideways(); // 输出 // drive sideways
Mixin有助于减少系统中的重复功能及增加函数复用。当一个应用程序可能需要在各种对象实例中共享行为时,我们可以通过在Mixin中维护这种共享功能并专注于仅实现系统中真正不同的功能,来轻松避免任何重复。
Mixin的缺点稍有争议,有些开发人员认为将功能注入对象原型中是一种很糟糕的想法,因为它会导致原型污染和函数起源方面的不确定性。
12.Decorator(装饰者)模式
通常,Decorator提供了将行为动态添加至系统的现有类的能力。其想法是,装饰本身对于类原有的基本功能来说并不是必要的;否则,它就合并到超类本身了。
下面例子只是一个简单的例子
// Decorator pattern function Vehicle(vehicleType) { this.vehicleType = vehicleType || "car"; this.model = "default"; this.license = "00000-000"; } // 测试基本的Vehicle实例 var testInstance = new Vehicle("car"); console.log(testInstance); // 创建一个Vehicle实例进行装饰 var truck = new Vehicle("truck"); // 给truck装饰新功能 truck.setModel = function(modelName) { this.model = modelName; } truck.setColor = function(color) { this.color = color; } // 测试赋值是否正常工作 truck.setModel("CAT"); truck.setColor("blue"); console.log(truck);
对象可以被新的行为包装或装饰,然后可以继续被使用,而不必担心被修改的基本对象。
jQuery.extend()允许我们在运行时或者在随后一个点上动态地将两个或两个以上的对象(和它们的属性)一起扩展(或合并)为一个单一对象。在这种情况下,一个目标对象可以用新功能来装饰,而不会在源/超类对象中破坏或重写现有的方法。
JavaScript设计模式的博客就先到这里,之后如果学习了新的模式,会及时补充到这篇文章中~~