javascript 设计模式(史上最全设计模式详解)

时间:2022-08-25 15:25:02

原文链接:http://www.cnblogs.com/Darren_code/archive/2011/08/31/JavascripDesignPatterns.html

基本知识:

 /*
单例模式
*/
var singleton = function (fn) {
var result;
//闭包
return function () {
return result || (result = fn.apply(this, arguments));
}
};

var createMask = singleton(function () {
return document.body.appendChild(document.createElement('div'));
});


/*
工厂模式
*/
function ObjectFactory() {
var obj = {};
Constructor = Array.prototype.shift.call(arguments);
obj.__proto__ = typeof Constructor.prototype === 'number' ? Object.prototype : Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'Object' ? ret : obj;

}


/*
观察者模式(面试者,简历,面试官)
*/
Events = function () {
var listen, obj, one, remove, trigger, __this;

obj = {};
__this = this;

listen = function (key, eventFn) {
//把简历扔进盒子,key是其联系方式
var stack, _ref;
//stack 是盒子
stack = (_ref = obj[key]) != null ? _ref : obj[key] = [];

return stack.push(eventFn);
};

one = function (key, eventFn) {
remove(key);
return listen(key, eventFn);
};

remove = function (key) {
var _ref;
return (_ref = obj[key]) != null ? _ref.length = 0 : void 0;
};

trigger = function () {
//面试官打电话通知面试者
var fn, stack, _i, _len, _ref, key;
key = Array.prototype.shift.call(arguments);
stack = (_ref = obj[key] )!= null ? _ref : obj[key] = [];
for (_i = 0, _len = stack.length; _i < _len; _i++) {
fn = stack[_i];
if (fn.apply(__this, arguments) == false) {
return false;
}
}
};

return {
listen: listen,
one: one,
remove: remove,
trigger: trigger
}
};

//demo
var adultTv = Events();
//订阅
adultTv.listen('play', function (data) {
alert('今天是' + data.name + '的电影');
});

//发布
adultTv.trigger('play', {name: '麻生希'});


/*
适配者模式:
适配器模式的作用就像是一个转接口,本来iphone的充电器是不能直接插在电脑机箱上的,而通过usb转接口就可以

设想一下这样的情景:
1.前端通过ajax请求,从后端获取来一份数据config.json, 并在此基础上构建了复杂的页面展示,
对这份文件做了逻辑处理。
2.后来因为业务的需求,后端需要重构这份文件的配置,不仅要扩展数据而且还要更改数据结构
3.不用说前端肯定没法工作了,难道需要重新构建逻辑做数据展示

这时不妨考虑下是适配者模式
var config = adapterConfig(config.json);
*/


/*
代理模式:
代理模式的定义是把对一个对象的处理,交给另一个代理对象来操作
实际的编程中,这种性能问题使用代理模式的机会是非常多的。比如频繁的访问dom节点,
频繁的请求远程资源,可以把操作先存到一个缓冲区中,然后自己选择真正的调用时机。
*//*
闭包:
1、闭包最常用的方式就是返回一个内联函数(内联函数即是在函数内部声明的函数)
2、在JavaScript中有作用域和执行环境的问题,在函数内部的变量在函数外部是无法访问的,在函数内部却可以得到全局变量
由于种种原因,我们有时候需要得到函数内部的变量,可是用常规的方法时得不到的,这时我们可以创建一个闭包,用来访问这个变量
3、闭包的用途,主要就是上一点提到的读取函数内部变量,还有一个作用就是可以使这些变量一直保存在内存中
4、使用闭包要注意,由于变量被保存在内存中,所以会对内存造成消耗,所以不能滥用闭包。
*/

//demo
function f(){
var n = 999;
function f1(){
alert(n+=1);
}
return f1;
}

/*
封装:
通过将一个方法或者属性声明为私用的,可以让对象的实现细节对其他对象保密以降低对象之间的耦合程度,可以保持数据的完整性
并对其修改方式加以约束,这样可以使代码更可靠,更易于调试。封装是面向对象的设计的基石。

JavaScript是一门面向对象的语言,可它并不具备将成员声明为公用或私用的任何内部机制,所以我们只能自己想办法实现这种特性。

私有属性和方法:函数作用域,在函数内用var关键字声明的变量在外部无法访问
特权属性和方法:创建属性和方法时使用this关键字,因为这些方法定义在构造器的作用域中,所以他们可以访问到私有属性和方法,
只有那些需要直接访问私有成员的方法才应该设计为特权方法

共有属性和方法:直接链在prototype上的属性和方法,不可以访问构造器内的私有成员,可以访问特权成员,子类会继承所有的共有方法
共有静态属性和方法:最好的理解方式就是把它想象成一个命名空间,实际上相当于把构造器作为命名空间来使用
*/

//demo
var _packaging = function(){
//私有属性和方法
var name = 'private name';
var method = function(){
//...
};

//特权属性和方法
this.title = 'js design patterns';
this.getName = function(){
return name;
};

//共有静态属性和方法
_packaging.name = 'public static name';
_packaging.alertName = function(){
alert(_packaging.name);
};

//共有属性和方法
_packaging.prototype = {
init: function(){
//...
}
}
};

/*
继承:
继承本身就是一个抽象的话题,在JavaScript中继承更是一个复杂的话题,JavaScript实现继承有两种方式:
1、类式继承
2、原型式继承
*/

//类式继承
//先声明一个类
function Person(name){
this.name = name;
}
//给这个超类的原型对象上天剑方法,getName
Person.prototype.getName = function(){
return this.name;
};

//实例化超类
var p = new Person('zhangsan');
alert(p.getName());

//声明子类
function Programmer(name, sex){
//这个类中药调用超类Person的构造函数,并将参数name传给它
Person.call(this, name);
this.sex = sex;
}

//这个子类的原型对象等于超类的实例
Programmer.prototype = new Person();
//因为子类的原型对象等于超类的实例,所以 Programmer.prototype.constructor 这个方法也等于超类构造函数,
//所以要从新赋值为自己自身
Programmer.prototype.constructor = Programmer;
//子类本身添加了getSex 方法
Programmer.prototype.getSex = function(){
return this.sex;
};

//实例化这个子类
var _m = new Programmer('lisi', 'male');
//自身ed方法
alert(_m.getSex);
//继承超类的方法
alert(_m.getName);

/* ---原型式继承--- */
//clone()函数用于创建新的类Person对象
var clone = function(obj){
var _f = function(){};
//这句是原型继承最核心的地方,函数的原型对象为对象字面量
_f.prototype = obj;
return new _f();
};

//先声明一个对象字面量
var Person = {
name: 'wangwu',
getName: function(){
return this.name;
}
};

var Programmer = clone(Person);/*
桥接模式:
桥接模式的要诀就是让接口'可桥梁',实际上也就是可配置,把抽象与其显示隔离开,
把页面中的一个个功能都想象成模块,接口可以使得模块之间的耦合度降低
*/
//demo


//异步请求资源的方法封装
function asyncRequest(type, url, callback){
//callback 为异步请求成功的回调
}

//给节点绑定事件的方法封装
function addEvent(ele, type, callback){

}

function getBeerById(id, callback){
asyncRequest('GET', 'beer.url?id='+id, function(resp){
callback(resp.responseText)
})
}

addEvent('div', 'click', getBeerByIdBridge);

function getBeerByIdBridge(e){
getBeerById(this.id, function(beer){
//beer 是从后台请求来的数据
console.log('request beer: ' + beer)
})
}
/*
装饰者模式:这个模式就是为对象增减功能
动态的给一个对象添加一些额外的职责,就扩展功能而言,它比生成子类方式更加灵活。
装饰者模式和组合模式有共同点,他们都与所包装的对象实现统一的接口,并且会把任何方法调用传递给这些对象,
可是组合模式用于把众多子对象组织为一个整体,而装饰者模式用于在不修改现有对象或从派生子类的前提下添加方法。
由于JavaScript 的对象可以随意扩展属性或者方法,所以装饰者模式实现起来很简单。
*/
//demo
var myText = {};
myText.Decorations = {};
myText.Core = function (str) {
this.show = function () {
return str;
}
};
//第一次装饰
myText.Decorations.addQuestionMark = function (strObj) {
this.show = function () {
return strObj.show() + '?';
}
};
//第二次装饰
myText.Decorations.makeItalic = function (strObj) {
this.show = function () {
return '
  • ' + strObj.show() + '
  • '
    }
    };

    //得到myText.Core 的实例
    var strObj = new myText.Core('this is a sample test String!');
    alert(strObj.show());
    strObj = new myText.Decorations.addQuestionMark(strObj);
    alert(strObj.show());
    strObj = new myText.Decorations.makeItalic(strObj);
    alert(strObj.show());


    /*
    组合模式:
    将对象组合成树形结构以表示'部分-整体'的层次结构,它使得客户对单个对象和复合对象的使用具有一致性。
    组合模式是一种专为创建web上的动态用户界面而量身定制的模式。使用这种模式,可以用一条命令在多个对象上
    激发复杂的或递归的行为,组合模式擅长于对大批对象进行操作。

    组合模式的好处:
    1.程序员可以用同样的方法处理对象的集合与其中的特定子对象
    2.它可以用来把一批子对象组织成树形结构,并且整棵树都可被遍历
    组合模式适用范围:
    1.存在一批组织成某处层次体系的对象
    2.希望对这批对象或其中的一部分对象实话一个操作

    其实组合模式就是将一系列相似或者相近的对象组合在一个大的对象,由这个大对象提供一些常用的接口对这些小对象进行操作,
    代码可重用,对外操作简单。
    */
    //demo
    var DynamicGallery = function (id) {
    //实现 Composite, GalleryItem 组合对象类
    this.children = [];
    this.element = document.createElement('div');
    this.element.id = id;
    this.element.className = 'dynamic-gallery';
    };

    DynamicGallery.prototype = {
    //实现Composite 组合对象接口
    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];
    },

    //实现DynamicGallery 组合对象接口
    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 GalleryImage = function(src){
    this.element = document.createElement('img');
    this.element.className = 'gallery-image';
    this.element.src = src;
    };

    GalleryImage.prototype = {
    add: function(){},
    remove: function(){},
    getChild: function(){},
    hide: function(){
    this.element.style.display = 'none';
    },
    show: function(){
    this.element.style.display = '';
    },

    //帮助方法
    getElement: function(){
    return this.element
    }
    };

    //使用
    var topGallery =new DynamicGallery('top-gallery');
    topGallery.add(new GalleryImage('/img/image-1.jpg'));
    topGallery.add(new GalleryImage('/img/image-2.jpg'));
    topGallery.add(new GalleryImage('/img/image-3.jpg'));
    var vacationPhotos =new DyamicGallery('vacation-photos');
    for(var i=0; i<30; i++){
    vacationPhotos.add(new GalleryImage('/img/vac/image-'+ i +'.jpg'));
    }
    topGallery.add(vacationPhotos);
    topGallery.show();
    vacationPhotos.hide();

    /*
    门面模式:几乎是所有JavaScript库的核心原则
    子系统中的一组接口提供一个一直的界面,门面模式定义了一个高层的接口,这个接口使得这一子系统更加容易使用,
    简单的说这是一种组织性的模式,它可以用来修改类和对象的接口,使其更方便使用。
    作用:
    1、简化类的接口
    2、消除类与使用它的客户代码之间的耦合

    该模式在处理浏览器差异时,比较适用
    */

    //demo
    var addEvent =function(el,type,fn){
    if(window.addEventListener){
    el.addEventListener(type,fn);
    }else if(window.attachEvent){
    el.attachEvent('on'+type,fn);
    }else{
    el['on'+type] = fn;
    }
    };
    //_model.util 是一个命名空间
    var _model = {util:{}};
    _model.util.Event = {
    getEvent: function(e){
    return e || window.event;
    },

    getTarget: function(e){
    return e.target || e.srcElement;
    },

    preventDefault: function(e){
    if(e.preventDefault){
    e.preventDefault();
    }else{
    e.returnValue = false;
    }
    }
    };
    //事件工具大概就是这么一个套路,然后结合addEvent函数使用
    addEvent(document.getElementsByTagName('body')[0],'click',function(e){
    alert(_model.util.Event.getTarget(e));
    });

    /*
    适配器模式:
    将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类
    可以一起工作,使用这种模式的对象又叫包装器,因为他们是在一个新的接口包装另一个对象。

    适用场景:
    适配多个js库
    */

    /*
    享元模式:
    运用共享技术有效的支持大量细粒度的对象。
    享元模式可以避免大量非常类似的开销。在程序设计中有时需要生成大量细粒度的类实例来表示数据。
    如果发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度的减少需要实例化的类的数量。
    如果能把这些参数迁移到类实例外面,在方法调用时将他们传递进来,就可以通过共享大幅度地减少单个实例的数目。

    组成部分:
    1.享元:抽离出来的外部操作对象
    2.工厂:创造对象的工厂
    3.存储器:存储实例的对象或数组,供享元来统一控制和管理

    应用场景:
    1.页面存在大量资源密集型对象
    2.这些对象具备一定的共性,可以抽离出公用的操作和数据

    关键:
    1.合理划分内部和外部数据。既要保持每个对象的模块性,保证享元的独立,可维护,又要尽可能多的抽离外部数据
    2.管理所有实例,既然抽离出了外部数据和操作,那享元就必须可以访问和控制实例对象。在JavaScript这种动态语言
    中,这个需求是很容易实现的:我们可以把工厂生产出来的对象简单的仍在一个数组中,为每个对象设计暴露给外部的方法,
    便于享元的控制。

    优点:
    1.将能耗大的操作抽离成一个,在资源密集型系统中,可大大减少资源和内存占用
    2.职责封装,这些操作独立修改和维护

    缺点:
    1、增加了实现复杂度,将原本由一个工厂方法实现的功能,修改为了享元+一个工厂+一个存储器
    2、对象数量少的情况,可能会增大系统开销

    */

    //demo
    //汽车登记示例
    var Car = function(make, model, year, owner, tag, renewDate){
    this.make = make;
    this.model = model;
    this.year = year;
    this.owner = owner;
    this.tag = tag;
    this.renewDate = renewDate;
    };

    Car.prototype = {
    getMake: function(){
    return this.make;
    },

    getModel: function(){
    return this.model;
    },

    getYear: function(){
    return this.year;
    },

    transferOwner: function(owner, tag, renewDate){
    this.owner = owner;
    this.tag = tag;
    this.renewDate = renewDate;
    },

    renewRegistration: function(renewDate){
    this.renewDate = renewDate;
    }
    };

    //享元模式
    //包含核心数据的Car类
    var Car = function(make, model, year){
    this.make = make;
    this.model = model;
    this.year = year;
    };

    Car.prototype = {
    getMake: function(){
    return this.make;
    },

    getModel: function(){
    return this.model;
    },

    getYear: function(){
    return this.year;
    }
    };

    //中间对象,用来实例化Car类
    var CarFactory = (function(){
    var createdCars = {};
    return {
    createCar: function(make, model, year){
    var car = createdCars[make+'-'+model+'-'+year];
    return car ? car : createdCars[make+'-'+model+'-'+year] = (new Car(make, model, year))
    }
    }
    })();

    //抽象工厂,用来处理Car的实例化和整合附加数据
    var CarRecordManager = (function(){
    var carRecordDatabase = {};
    return {
    addCarRecord: function (make, model, year, owner, tag, renewDate) {
    var car = CarFactory.createCar(make, model, year);
    carRecordDatabase[tag] = {
    owner: owner,
    tag: tag,
    renewDate: renewDate,
    car: car
    }
    },

    transferOwnership: function(tag, newOwner, newTag, newRenewDate){
    var record = carRecordDatabase[tag];
    record.owner = newOwner;
    record.tag = newTag;
    record.renewDate = newRenewDate;
    },

    renewRegistration: function(tag, newRenewDate){
    carRecordDatabase[tag].renewDate = newRenewDate;
    },

    getCarInfo: function(tag){
    return carRecordDatabase[tag]
    }
    }
    })()