JS常用设计模式
大型单页应用里,复杂度上升到一定程度时,没有适当的设计模式进行降耦,后续的开发也难以下手。
而设计模式正是为了降耦而存在。
单例模式
单例模式的核心是确保只有一个实例,并且提供全局访问。
特点
满足“单一职责原则” : 使用代理模式,不在构造函数中判断是否已经创建过该单例;
满足惰性原则
应用
弹出登陆窗口。
实例
var getSingle = function (fn) {
var res;
return function() {
return res || (res = fn.apply(this, arguments));
}
}
var createPopup() {
var div = document.createElement('div');
div.innerHTML = "Login window";
div.style.display = "none";
document.body.appendChild(div);
return div;
}
var createLoginPopup = getSingle(createPopup);
//create popup div here by using a given function, 满足两个原则
document.getElementById("loginBt").onclick = function() {
var popup = createLoginPopup();
pop.style.display = "block";
}
构造函数模式
/**
* 构造一个动物的函数
*/
function Animal(name, color){
this.name = name;
this.color = color;
this.getName = function(){
return this.name;
}
}
// 实例一个对象
var cat = new Animal('猫', '白色');
console.log( cat.getName() );
原型模式
function Person(){
}
Person.prototype.name = "bill";
Person.prototype.address = "GuangZhou";
Person.sayName = function (){
alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
//测试代码
alert(person1.name); // bill
alert(person2.name); // bill
person1.sayName(); //bill
person2.sayName(); //bill
person1.name = "666";
alert(person1.name); // 666
alert(person2.name); // bill
person1.sayName(); //666
person2.sayName(); //bill
混合模式
/**
* 混合模式 = 原型模式 + 构造函数模式
*/
function Animal(name, color){
this.name = name;
this.color = color;
console.log( this.name + this.color)
}
Animal.prototype.getInfo = function(){
console.log('名称:'+ this.name);
}
function largeCat(name, color){
Animal.call(null, name, color);
this.color = color;
}
largeCat.prototype = create(Animal.prototype);
function create (parentObj){
function F(){}
F.prototype = parentObj;
return new F();
};
largeCat.prototype.getColor = function(){
return this.color;
}
var cat = new largeCat("Persian", "白色");
console.log( cat )
工厂模式
工厂:函数内部产生b对象并返回。
1.
function a(name){
var b = new object();
b.name = name;
b.say = function(){
alert(this.name);
}
return b
}
2.
function Animal(opts){
var obj = new Object();
obj.name = opts.name;
obj.color = opts.color;
obj.getInfo = function(){
return '名称:'+obj.name +', 颜色:'+ obj.color;
}
return obj;
}
var cat = Animal({name: '波斯猫', color: '白色'});
cat.getInfo();
简单工厂模式
简单工厂模式的理念就是创建对象,对不同类的实例化;只需要创建一个对象,然后通过对这个对象大量的方法和属性,并在最终将对象返回出来
//basketball base class
var Baseketball = function(){
this.intro = 'baseketball is hotting at unitedstates';
}
Baseketball.prototype = {
getMember : function(){\
console.log('each team needs five players');
},
getBallSize : function(){
console.log('basketball is big');
}
}
//football base class
var Football = function(){
this.intro = 'football is popular at all of the world';
}
Football = function(){
getMember = function(){
},
getBallSize = function(){
}
}
//sport factory
var SportsFactory = function(name){
switch(name){
case 'NBA':
return new Baseketball();
case 'wordCup':
return new Football();
}
}
//when you want football
var football = SportsFactory('wordCup');
console.log(football);
console.log(football.intro);
football.getMember();
迭代器模式
装饰者模式
策略模式
定义一个个可以相互替换的算法,并且把他们封装起来。
特点
- 符合开放-封闭原则 : 要修改使用的算法时不用深入函数内部进行修改,只需修改策略类;
- 将算法的实现与使用分离开,提高算法复用性;
- 通过组合、委托和多态避免多重条件选择语句;
应用
动画实现不同的缓动效果。
一般分为两个部分:策略类于环境类。策略类用于封装各种算法,并且负责具体的计算过程; 环境类负责接收用户的请求,并且把请求委托给某一个策略类。因为各个策略类实现的算法和计算的结果不同,而环境类调用策略类的方法却是相同的,这就体现了多态性。要想实现不同的算法,只需要替换环境类中的策略类即可。
在js中,我们不必构造策略类,可直接使用函数作为策略对象。
示例
var strategies = {
"s1": function() {
//algo 1
},
"s2": function() {
//algo 2
},
"s3": function() {
//algo 3
}
}
var someContext = new SomeConext();
someContext.start("s1"); //using s1 to calculate
//someContext.add("s1"); or add s1 as a rule for validation
外观模式
也可译为门面模式。它为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。引入外观角色之后,使用者只需要直接与外观角色交互,使用者与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
比如在家要看电影,需要打开音响,再打开投影仪,再打开播放器等等,引入外观角色之后,只需要调用“打开电影设备”方法就可以。外观角色封装了打开投影仪等操作,给使用者提供更容易使用的方法。
作用
- 简化复杂接口
- 解耦和,屏蔽使用者对子系统的直接访问
实例
在形式上,外观模式在javascript中就像这样:
function a(x){
// do something
}
function b(y){
// do something
}
function ab( x, y ){
a(x);
b(y);
}
下面的一个例子,把阻止冒泡和阻止默认事件放到了外观角色中:
var N = window.N || {};
N.tools = {
stopPropagation : function( e ){
if( e.stopPropagation ){
e.stopPropagation();
}else{
e.cancelBubble = true;
}
},
preventDefault : function( e ){
if( e.preventDefault ){
e.preventDefault();
}else{
e.returnValue = false;
}
},
stopEvent : function( e ){
N.tools.stopPropagation( e );
N.tools.preventDefault( e );
}
外观模式在javascript的应用主要可以分为两类,某块代码反复出现,比如函数a的调用基本都出现在函数b的调用之前,那么可以考虑考虑将这块代码使用外观角色包装一下来优化结构。还有一种就是对于一些浏览器不兼容的API,放置在外观内部进行判断,处理这些问题最好的方式便是将跨浏览器差异全部集中放置到一个外观模式实例中来提供一个对外接口。
代理模式
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
虚拟代理
虚拟代理是把一些开销很大的对象,延迟到真正需要它的时候才去创建执行
图片懒加载
//图片加载
let imageEle = (function(){
let node = document.createElement('img');
document.body.appendChild(node);
return {
setSrc:function(src){
node.src = src;
}
}
})();
//代理对象
let proxy = (function(){
let img = new Image();
img.onload = function(){
imageEle.setSrc(this.src);
};
return {
setSrc:function(src){
img.src = src;
imageEle.setSrc('loading.gif');
}
}
})();
proxy.setSrc('example.png');
合并http请求
如果有一个功能需要频繁进行请求操作,这样开销比较大,可以通过一个代理函数收集一段时间内请求数据,一次性发出
//上传请求
let upload = function(ids){
$.ajax({
data: {
id:ids
}
})
}
//代理合并请求
let proxy = (function(){
let cache = [],
timer = null;
return function(id){
cache[cache.length] = id;
if(timer) return false;
timer = setTimeout(function(){
upload(cache.join(','));
clearTimeout(timer);
timer = null;
cache = [];
},2000);
}
})();
// 绑定点击事件
let checkbox = document.getElementsByTagName( "input" );
for(var i= 0, c; c = checkbox[i++];){
c.onclick = function(){
if(this.checked === true){
proxy(this.id);
}
}
}
缓存代理
缓存代理可以作为一些开销大的运算结果提供暂时的存储,下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果
//计算乘积
let mult = function(){
let result = 1;
for(let i = 0,len = arguments.length;i < len;i++){
result*= arguments[i];
}
return result;
}
//缓存代理
let proxy = (function(){
let cache = {};
reutrn function(){
let args = Array.prototype.join.call(arguments,',');
if(args in cache){
return cache[args];
}
return cache[args] = mult.apply(this,arguments);
}
})();
优缺点
1.优点:代理模式能将代理对象与被调用对象分离,降低了系统的耦合度。代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用。代理对象也可以对目标对象调用之前进行其他操作。
2.缺点:增加了系统的复杂度
观察者模式
模块模式
/**
* 模块模式 = 封装大部分代码,只暴露必需接口
*/
var Car = (function(){
var name = '法拉利';
function sayName(){
console.log( name );
}
function getColor(name){
console.log( name );
}
return {
name: sayName,
color: getColor
}
})();
Car.name();
Car.color('红色');