设计模式是广大开发者的实践总结,力求写出可维护、复用性和可读性高的代码,设计模式为特定环境下的常见问题提供了一个组织结构。
模块设计模式
JS模块化是使用最普遍的设计模式,用于保持特殊的代码块与其它组件之间互相独立。为支持结构良好的代码提供了松耦合。
考虑到私有的作用域,模块应该是一个立即调用函数(IIFE) ,也就是说,它是一个保护其私有变量和方法的闭包。
基本结构如下:
(function() {
// declare private variables and/or functions
return {
// declare public variables and/or functions
}
})();
上面的结果是返回一个对象,需要用一个变量名接受,很多时候我们也可以直接在闭包内部直接将对象挂载到全局对象中,具体参考 如何构建自己的工具函数库
原型设计模式
原型模式主要用于为高性能环境创建对象。
基本结构如下:
function Tool(){
this.attr = 'TEST'
}
Tool.prototype.fun1 = function(){
}
Tool.prototype.fun2 = function(){
}
还可以这样写,但是个人不是很推荐,因为它用覆盖原型的方式,会破坏原本有的属性,比如都有的constructor
Tool.prototype = {
fun1:function(){
},
fun2:function(){
}
}
观察者设计模式
当应用的一部分改变了,另一部分也需要相应更新。最典型的例子就是在Angular
中,如果 $scope
被更新,就会触发一个事件去通知其他组件。结合观察者模式就是:如果一个对象改变了,它只要派发 broadcasts
事件通知依赖的对象它已经改变了则可。
在观察者模式中,subject
、observer
, and concrete objects
是必不可少的。 subject
包含对每个具体观察者的引用,以便传递改动信息。观察者本身是一个抽象的类,使得具体的观察者可以执行通讯方法。
在看到观察者模式众多优点的同时,我们必须注意到它的一个缺点:随着观察者数量的增加,应用的性能会大大降低。大家都比较熟悉的观察者就是 watchers 。 在AngularJS中,我们可以 watch 变量、方法和对象。$digest 循环更新,当一个作用域内对象被修改时,它就把新的值告诉每个监听者。
比较概念的解释是,目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。
// 目标
var Subject = function() {
this.observers = [];
return {
observers: this.observers,
subscribeObserver: function(observer) {
this.observers.push(observer);
},
unsubscribeObserver: function(observer) {
var index = this.observers.indexOf(observer);
if(index > -1) {
this.observers.splice(index, 1);
}
},
notifyObserver: function(observer) {
var index = this.observers.indexOf(observer);
if(index > -1) {
this.observers[index].notify(index);
}
},
notifyAllObservers: function() {
for(var i = 0; i < this.observers.length; i++){
this.observers[i].notify(i);
};
}
};
};
// 观察者
var Observer = function() {
return {
notify: function(index) {
console.log("Observer " + index + " is notified!");
}
}
}
var subject = new Subject();
var observer1 = new Observer();
var observer2 = new Observer();
var observer3 = new Observer();
var observer4 = new Observer();
subject.subscribeObserver(observer1);
subject.subscribeObserver(observer2);
subject.subscribeObserver(observer3);
subject.subscribeObserver(observer4);
subject.notifyObserver(observer2); // Observer 2 is notified!
subject.notifyAllObservers();
// Observer 1 is notified!
// Observer 2 is notified!
// Observer 3 is notified!
// Observer 4 is notified!
上述直接将观察者列表写在目标内,也可以将观察者列表抽象出来,目标依赖观察者列表。
发布、订阅模式
发布、订阅模式是采用一个话题
来绑定发布者和订阅者之间的关系,订阅者接收事件通知,发布者派发事件。该事件系统支持定义特殊应用的事件,可以传递包含订阅者本身需要的自定义参数。这样做主要是为了避免订阅者和发布者之间的依赖。
这里有别于观察者模式的是,任何订阅者都可以通过恰当的事件处理器来注册并接受发布者广播的通知
。
发布订阅模式中的订阅者是通过一些通讯媒介被告知的,而观察者则是通过执行其事件处理器来获得消息通知。
比较概念的解释是,订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。
因此我们需要一个调度中心,简易代码如下:
var event = {
list: [],
listen: function(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
}
// 订阅的消息添加到缓存列表中
this.list[key].push(fn);
},
trigger: function() {
var key = Array.prototype.shift.call(arguments);
var fns = this.list[key];
// 如果没有订阅过该消息的话,则返回
if (!fns || fns.length === 0) {
return;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);
}
},
remove: function(key,fn) {
var fns = this.list[key];
// 如果key对应的消息没有订阅过的话,则返回
if (!fns) {
return false;
}
// 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
if (!fn) {
fn && (fns.length = 0);
} else {
for (var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i];
if (_fn === fn) {
fns.splice(i, 1); // 删除订阅者的回调函数
}
}
}
}
};
// 这个函数使所有的普通对象都具有发布订阅功能
var initEvent = function(obj) {
for (var i in event) {
obj[i] = event[i];
}
};
单例模式
单例模式只允许实例化一个对象,但是相同的对象,会用很多个实例。单例模式制约着客户端创建多个对象。第一个对象创建后,就返回实例本身。
单例模式在 AngularJS 相当流行,最常见的是作为 services、factories、和 providers。它们维护状态,提供资源访问,创建两个实例摆脱一个共享的service/factory/provider。
在多线程的应用中,当多个线程尝试去访问同个资源时,就会出现竞争状态
。因此,开发者在多线程应用里面使用单例模式时,必须清楚同步性。
基础代码如下:
var printer = (function () {
var printerInstance;
function create () {
function print() {
// underlying printer mechanics
}
function turnOn() {
// warm up
// check for paper
}
return {
// public + private states and behaviors
print: print,
turnOn: turnOn
};
}
return {
getInstance: function() {
if(!printerInstance) {
printerInstance = create();
}
return printerInstance;
}
};
})();