今天开始讲解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种设计模式终于讲解的差不多了。能看完到最后的人都是好样的,希望对你们能有所帮助。