今天开始讲解JavaScript设计模式,作为以前学Java的我在设计模式上或多或少接触过,可能自己理解程度有限,望大家多多见谅。 我准备讲20种设计模式,分别有:
- 01.单例模式
- 02.工厂模式
- 03.桥接模式
- 04.装饰者模式
- 05.组合模式
- 06.外观模式
- 07.适配器模式
- 08.代理模式
- 09.观察者模式
- 10.享元模式
- 11.状态模式
- 12.命令模式
- 13.职责链模式
- 14.构造函数模式
- 15.建造者模式
- 16.策略模式
- 17.迭代器模式
- 18.中介者模式
- 19.模板方法模式
- 20.原型模式
尽管讲解的模式很多,但是也不需要一一去记。因为学设计模式的目的并不是为了记住它的模式叫什么名,而是记住每个模式的代码为什么这样写,这样写的好处是什么。 当自己以后在看js插件源码或项目代码时,你能看出整个项目的架构,方便自己理解,那么JavaScript设计模式对你来说就非常有用。 我会把每个模式按照平时使用程度进行星级划分,星级低的不代表不重要,只是说在编写一般代码可能用的不多,但在编写架构代码却很常见。
1.单例模式(★★★★★)
var channelList = {
page: 'channelList',
init: function () {
//code
},
pageDidShow: function () {
//code
},
pageDidHide: function () {
//code
},
pageDestory: function () {
//code
},
}
总结:
特点及优势:单例模式就是保证一个类只有一个实例,其目的就是为了节约资源。 适用场合:用来划分命名空间。 划分命名空间的好处有以下两点:1.可以减少网页中全局变量的数量(即window下面的变量)2.可以在多人开发时避免代码的冲突
2.工厂模式(★★★★☆)
Ajax模块:
var XMLHttpFactory = {}
XMLHttpFactory.createXMLHttp = function () {
var XMLHttp = null
if (window.XMLHttpRequest) {
XMLHttp = new XMLHttpRequest()
} else if (window.ActiveXObject) {
XMLHttp = new ActiveXObject('Microsoft.XMLHTTP')
}
return XMLHttp
}
简单工厂模式:
var Page={};
Page.APage=function(){
//code
}
Page.BPage=function(){
//code
}
Page.CPage=function(){
//code
}
...
Page.factory=function(type){
return new Page[type];
}
var a=Page.factory('APage');
var b=Page.factory('BPage');
总结:
特点及优势:工厂模式无需使用new关键字指定具体类,而是在创建对象时才确定类。有助于创建模块化的代码,方便扩展。 适用场合:a.主要用在所实例化的类的类型不能在开发期间确定,而只能在运行期间才能确定的情况下。b.想创建一些包含成员对象的类但又不想把它们紧密耦合在一起。
3.桥接模式(★★★☆☆)
封装的东西:
function extend(parent, child) {
var child = child || {}
for (var i in parent) {
if (typeof parent[i] === 'object') {
child[i] = parent[i].constructor === Array ? [] : {}
extend(parent[i], child[i])
} else {
child[i] = parent[i]
}
}
return child
}
桥接模式代码:
function SoftWare() {
this.start = function () {}
}
var qq = {},
weibo = {}
extend(SoftWare, qq)
extend(SoftWare, weibo)
qq.start = function () {
console.log('使用QQ软件')
}
weibo.start = function () {
console.log('使用微博软件')
}
function System() {
this.run = function (software) {}
}
var Android = {},
IOS = {}
extend(System, Android)
extend(System, IOS)
Android.run = function (software) {
console.log('在安卓系统下')
software.start()
}
IOS.run = function (software) {
console.log('在苹果系统下')
software.start()
}
Android.run(qq)
IOS.run(weibo)
总结:
特点及优势:桥接模式是将抽象与其实现分离开来,以便二者独立变化。促进代码的模块化,促成更简洁的实现并提高抽象的灵活性。 桥接模式的参与者包括:抽象类、具体类、实现者、具体实现者。 适用场合:把一组类和函数连接起来。
4.装饰者模式(★★☆☆☆)
function MacBook() {
this.hasSoftware = function () {
console.log('have software')
}
this.cost = function () {
return 8950
}
}
function AppleMouse(macbook) {
this.isMove = function () {
console.log('is Move')
}
this.cost = function () {
return macbook.cost() + 450
}
}
function Pasting(macbook) {
this.cost = function () {
return macbook.cost() + 200
}
}
var myMacBook = new Pasting(new AppleMouse(new MacBook()))
myMacBook.cost() //9600
总结:
特点及优势:装饰者模式用于包装同接口的对象,通过重载方法的形式添加新功能。 适用场合:在为对象添加新特性时,使用了大量子类或不想改变使用该对象的代码的话,使用装饰者模式。
5.组合模式(★★★☆☆)
先创建树干:
var UL = function (id) {
this.children = []
this.element = document.createElement('ul')
this.element.id = id
}
UL.prototype = {
add: function (child) {
this.children.push(child)
this.element.appendChild(child.getElement())
},
remove: function (child) {
for (var node, i = 0; (node = this.getChild(i)); i++) {
if (node == child) {
this.children.splice(i, 1)
break
}
}
this.element.removeChild(child.getElement())
},
getChild: function (i) {
return this.children[i]
},
hide: function () {
for (var node, i = 0; (node = this.getChild(i)); i++) {
node.hide()
}
this.element.style.display = 'none'
},
show: function () {
this.element.style.display = 'block'
for (var node, i = 0; (node = this.getChild(i)); i++) {
node.show()
}
},
getElement: function () {
return this.element
},
}
再创建树叶:
var Li = function (text) {
this.element = document.createElement('li')
var text = document.createTextNode(text)
this.element.appendChild(text)
}
Li.prototype = {
add: function () {},
remove: function () {},
getChild: function () {},
hide: function () {
this.element.style.display = 'none'
},
show: function () {
this.element.style.display = 'block'
},
getElement: function () {
return this.element
},
}
最后实现:
document.body.innerHTML = ''
var li1 = new Li('列表一')
var li2 = new Li('列表二')
var ul = new UL('ww')
ul.add(li1)
ul.add(li2)
document.body.appendChild(ul.element)
ul.hide()
总结:
特点及优势:组合模式把一批子对象组织成树形结构,只需一条命令就可以操作树中的所有对象。 适用场合:特别适合于动态的HTML用户界面。
6.外观模式(★★★★★)
var addEvent=function(el,ev,fn){
if(el.addEventListener){
el.addEventListener(ev,fn,false);
}else if(el.attachEvent){
el.attachEvent('on'+ev,fn);
}else{
el['on'+ev]=fn;
}
};
var Event={
getEvent:function(e){
return e||window.event;
},
getTarget:function(e){
return e.target||e.srcElement;
},
stopPropagation:function(e){
if(e.stopPropagation){
e.stopPropagation();
}else{
e.cancelBubble=true;
}
},
preventDefault:function(e){
if(e.preventDefault){
e.preventDefault();
}else{
e.retrunValue=false;
}
}
stop: function (e) {
this.preventDefault(e);//阻止默认行为
this.stopPropagation(e);//阻止默认冒泡
}
};
var $=function(selector){
return document.querySelectorAll(selector);
}
总结:
特点及优势:外观模式是几乎所有JavaScript库(如JQuery、YUI、Prototype.js)的核心原则。外观模式可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。 适用场合:a.封装一些兼容浏览器的接口。b.简化重复性代码。
7.适配器模式(★★★★☆)
var param = { name: 'ww', age: 2 }
function people(name, age) {}
function adaptePeople(param) {
people(param.name, param.age)
}
adaptePeople(param)
总结:
特点及优势:适配器模式是用一个新接口对现有的接口进行封装,好处在于无需对现有代码做大改动。 使用场合:现有接口,但其方法或属性不符合你的要求。
8.代理模式(★★★☆☆)
function FangDong() {
this.room = '1号房间'
}
FangDong.prototype.chuzu = function () {
console.log('房东出租' + this.room)
}
function Proxy() {
this.isInited = false
this.fangdong = null
}
Proxy.prototype = {
init: function (callback) {
var self = this
if (this.fangdong == null) {
setTimeout(function () {
self.fangdong = new FangDong()
console.log('终于等到房东想租出去')
callback()
self.isInited = true
}, 5000)
}
this.interval = setInterval(function () {
self.checkInit()
}, 100)
},
checkInit: function () {
if (this.isInited) {
clearInterval(this.interval)
}
},
chuzu: function () {
var self = this
this.init(function () {
self.fangdong.chuzu()
console.log('出租后收中介费')
})
},
}
var proxy = new Proxy()
proxy.chuzu()
总结:
特点及优势:代理模式控制对创建开销很大资源的对象的访问。 比如例子中讲到的中介如果没收到房东的委托就没有出租房,只有等有房东需要让中介帮忙,才能有房子,才能有钱赚,所有创建房东就需要时间。 适用场合:包装那些需要大量计算或较长时间才能实例化的类。
9.观察者模式(★★★★☆)
var PubSub={};
(function(p){
var topics={},lastUid=-1;
p.publish=function(topic,data){
if(!topics.hasOwnProperty(topic)){
return false;
}
var subscribers=topics[topic];
for(var i=0,j=subscribers.length;i < j;i++){
subscribers[i].func(topic,data);
}
return true;
};
p.subscribe=function(topic,func){
if(!topics.hasOwnProperty(topic)){
topics[topic]=[];
}
var token=(++lastUid).toString();
topics[topic].push({token:token,func:func});
return token;
};
p.unsubscribe=function(token){
for(var m in topics){
if(topics.hasOwnProperty(m)){
for(var i=0,len=topics[m].length;i < len;i++){
if(topics[m][i].token === token){
topics[m].splice(i,1);
return token;
}
}
}
}
};
}(PubSub));
下面是用JQuery的on/off功能实现:
(function($){
var o=$({});
$.subscribe=function(){
o.on.apply(o,arguments);
};
$.unsubscribe=function(){
o.off.apply(o,arguments);
};
$.publish=function(){
o.trigger.apply(o, arguments);
};
} (jQuery));
总结:
特点及优势:DOM的事件监听器(addEventListener)就是一种内置的观察者。 观察者模式的执行过程:首先订阅特定的事件,然后等待事件的发生,当事件发生时,订阅方的回调函数会得到通知并执行。 因此观察者模式包括订阅、退订、发布方法。 适用场合:用于事件监听方面的优化。使用该模式可以削减事件注册监听的次数,让可观察对象借助一个事件监听器替你处理各种行为,从而降低内存消耗和提高互动性能。
10.享元模式(★★☆☆☆)
$('div').bind('click', function () {
console.log($(this).attr('id')) //bad
console.log(this.id) //good
})
总结:
特点及优势:享元模式可以大幅度减少需要实例化的类的数量,提升内存的性能。 如上面例子中的代码$(this)表示生成JQuery对象,意味着每次点击都会重新创建JQuery对象,这样就显得很不科学。 适用场合: 页面存在大量资源密集型对象,对浏览器的内存和CPU占用极大的网站。
11.状态模式(★★★☆☆)
var StateManager = function () {
var states = {
ready: function (state) {
console.log('开始下载')
},
downloading: function (state) {
console.log('下载中')
},
downloadPasued: function (state) {
console.log('下载暂停')
},
downloaded: function (state) {
console.log('下载完毕')
},
downloadFail: function (state) {
console.log('下载失败')
},
},
changeState = function (state) {
states[state] && states[state]()
}
return {
changeState: changeState,
}
}
var stateManager = StateManager()
stateManager.changeState('ready')
总结:
特点及优势:状态模式可以把散落在世界各地的条件分支集中管理到一个类里,并且可以很容易的添加一种新的状态。 适用场合:a.一个操作中含有庞大的条件分支语句。 b.一个对象的行为取决于它的状态。
12.命令模式(★★★★☆)
var $ = {
ajax: function (data) {
console.log(data)
data.success('请求后响应的数据')
},
}
var AjaxInterface = {
doData: function (operName, param, callback) {
$.ajax({
type: 'POST',
url: '/user/' + operName,
dataType: 'json',
contentType: 'application/json; charset=utf-8',
cache: false,
data: param,
success: function (data) {
callback.call(this, data)
},
})
},
add: function (param, callback) {
this.doData('add', param, function (data) {
console.log('添加的数据:' + data)
callback.call(this, data)
})
},
get: function (param, callback) {
this.doData('get', param, function (data) {
console.log('查询的数据:' + data)
callback.call(this, data)
})
},
update: function (param, callback) {
this.doData('update', param, function (data) {
console.log('修改的数据:' + data)
callback.call(this, data)
})
},
}
var param = {
objName: '用户',
id: 1,
}
AjaxInterface.get(param, function (data) {
console.log(data)
})
总结:
特点及优势:命令模式将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化。简单说命令模式就是对相似方法的封装。 适用场合:a.代码封装。 b.代码重构。
13.职责链模式(★★☆☆☆)
function Handler(successor) {
this.successor = successor || null
}
Handler.prototype.handler = function () {
if (this.successor) {
this.successor.handler()
}
}
var p = new Handler({
handler: function () {
console.log('p')
},
})
var div = new Handler(p)
var body = new Handler(div)
p.handler()
div.handler()
body.handler() //都是调用原型handler方法
var p2 = new Handler({
handler: function () {
console.log('p2')
},
})
var div2 = new Handler(p2)
div2.handler = function () {
Handler.prototype.handler.apply(this)
console.log('div2')
}
var body2 = new Handler(div2)
body2.handler = function () {
Handler.prototype.handler.apply(this)
console.log('body2')
}
body2.handler() //p2、div2、body2
总结:
特点及优势:JavaScript内部的事件冒泡和事件捕获用到了职责链模式。 适用场合:A对象请求B对象,B对象不处理请求C对象,C对象不处理清楚D对象。对事件处理程序过多的代码解决方式就使用职责链模式。
14.构造函数模式(★★★★★)
function People(name, age) {
if (!(this instanceof People)) {
return new People(name, age)
}
this.name = name
this.age = age
}
People.prototype.say = function () {
console.log('My name is ' + this.name)
}
var p1 = new People('ww', 22)
p1.say()
var p2 = People('zl', 22)
p2.say()
总结:
特点及优势:构造函数用于创建特定类型的对象。 适用场合:创建新的类。
15.建造者模式(★★★☆☆)
$.ajax(function(){
url:'',
type:'',
success:function(data){}
});
$('<div id="ww"><span></span></div>');
总结:
特点及优势:建造者模式可以将一个复杂对象的构建与其表示相分离。建造者模式让你只用知道结果,不用知道创建的过程。 如上面代码中ajax的回调success里面的data,你不需要知道data的创建过程,你只需要使用即可;$里面只需要传入要生成的HTML字符,而不需要关心HTML对象是如何生产的。 适用场合:代码中需要分步骤构建一个复杂的对象。
16.策略模式(★★★☆☆)
var Valid = function (res) {
var validataList = {
isEmptyNull: function (val) {
if (val) {
return false
}
return true
},
isNumber: function (val) {
return /^-?\d+$/.test(val)
},
isMaxLength: function (val, maxLen) {
return (val + '').length <= maxLen ? maxLen : 0
},
isPassword: function (val) {
return /^(\d|[a-z]|[A-Z]){6,18}$/.test(val)
},
}
return {
validata: function (data) {
var count = 0,
total = 0
for (var i in res) {
total++
if (validataList[i](data, res[i]) == res[i]) {
count++
}
}
if (total == count) {
return true
}
return false
},
}
}
var mobileValid = new Valid({
isEmptyNull: false,
isNumber: true,
isMaxLength: 11,
})
var passwordValid = new Valid({
isEmptyNull: false,
isPassword: true,
})
var mobile = '13688888888',
password = 'ww123'
mobileValid.validata(mobile)
passwordValid.validata(password)
总结:
特点及优势:策略模式就是定义一系列的算法,把它们一个个封装起来,并且使它们可替换删减。 适用场合:a.封装算法。 b.封装几乎任何类型的规则。
17.迭代器模式(★★★★☆)
var $ = (function () {
var forEach = function (arr, callback) {
if (arr instanceof Array) {
for (var i = 0, len = arr.length; i < len; i++) {
callback(i, arr[i])
}
} else {
for (var i in arr) {
callback(i, arr[i])
}
}
}
return {
each: forEach,
}
})()
$.each([1, 2, 4, 6], function (i, val) {})
$.each({ a: 1, b: 2, c: 3 }, function (i, val) {})
总结:
特点及优势:迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该方法中的内部表示。 适用场合:遍历集合。
18.中介者模式(★★★★★)
var mode1 = Mode.create(),
mode2 = Mode.create()
var view1 = View.create(),
view2 = View.create()
var controler1 = Controler.create(mode1, view1, function () {})
var controler2 = Controler.create(mode2, view2, function () {})
总结:
特点及优势:控制层便是位于表现层与模型层之间的中介者。 中介者模式的功能就是封装对象之间的交互,降低了系统对象之间的耦合性,使得对象易于独立的被复用。 适用场合:一组定义良好的对象,现在要进行复杂的通信。
19.模板方法模式(★★★★☆)
var Life=function(){
this.DNA复制();
this.出生();
this.成长();
this.衰老();
this.死亡();
}
Life.prototype={
DNA复制:function(){//自己不能做主},
出生:function(){},
成长:function(){},
衰老:function(){},
死亡:function(){}
}
var Mammal=function(){
Life.apply(this,arguments);//继承生命
}
Life.prototype={
DNA复制:function(){//自己不能做主},
出生:function(){胎生();},
成长:function(){},
衰老:function(){},
死亡:function(){}
}
var People=function(){
Mammal.apply(this,arguments);//继承哺乳动物
}
总结:
特点及优势:模板方法模式预先定义一组算法,先把算法的不变部分抽象到父类,再将另外一些可变的步骤延迟到子类去实现。 模板方法是一种代码复用的基本技术,在类库中尤为重要,因为他们提取了类库中的公共行为。 适用场合:a.代码重构 b.代码架构
20.原型模式(★★★★☆)
function extend(child, parent) {
var Empty = function () {}
Empty.prototype = parent.prototype
child.prototype = new Empty()
child.prototype.constructor = child
}
总结:
特点及优势:原型模式是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。 适用场合:a.实现继承 b.声明公用方法
经历一周多的总结与编写,20种设计模式终于讲解的差不多了。能看完到最后的人都是好样的,希望对你们能有所帮助。