前端设计模式——有哪些怎么用
文章目录
- 程序是什么
- 实现需求思考步骤
- 创建型设计模式-创建对象
- 工厂模式
- 单例模式
- 建造者模式
- 构造函数模式
- 混合模式
- 模块间的沟通
- 观察者模式(订阅发布模式)
- 职责链模式
- 结构型设计模式
- 代理模式
- 性能优化(质量调优)
- 策略模式
- 享元模式
- 模块模式
程序是什么
程序 = 模块与模块之间的沟通
实现需求思考步骤
- 功能的主体对象如何创建
- 脱离代码,抽象思考,实现功能需要哪几步
- 回到代码,思考实现这几步,需要什么模块
- 组织模块沟通(选择哪种模块间的沟通模式)
- 实现模块(创建模块用哪种设计模式)
- 性能质量调优(可不可以用到哪种模式或方法,使得代码质量更好,更易维护和扩展)
创建型设计模式-创建对象
工厂模式
大量创建实例,为了不暴露创建对象的具体逻辑,将逻辑封装在一个函数中,这个函数就称为一个工厂。
实例:jquery中的$创建对象,vue
实例代码:
//安全模式创建的工厂方法函数
let UserFactory = function(role) {
if(this instanceof UserFactory) { //确保必须当前类的实例,否则重新创建一个类实例,防止变成window调用
var s = new this[role]();
return s;
} else {
return new UserFactory(role);
}
}
//工厂方法函数的原型中设置所有对象的构造函数
UserFactory.prototype = {
SuperAdmin: function() {
this.name = "超级管理员",
this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理']
},
Admin: function() {
this.name = "管理员",
this.viewPage = ['首页', '通讯录', '发现页', '应用数据']
},
NormalUser: function() {
this.name = '普通用户',
this.viewPage = ['首页', '通讯录', '发现页']
}
}
//调用
let superAdmin = UserFactory('SuperAdmin');
let admin = UserFactory('Admin')
let normalUser = UserFactory('NormalUser')
单例模式
确保一个类全局只有一个实例化对象,一般用于全局缓存,采用闭包的方式实现。
实例:vue-router和vuex
实例模型:
var Single = (function(){
var instance;
function Construct(){
//创建实例的构造代码块
}
return {
getInstance: function(){
if(!instance){ //确保全局只有一个
instance = new Construct();
}
return instance;
}
}
})()
建造者模式
将一个复杂的对象分解成多个简单的对象来进行构建,将复杂的构建层与表示层分离,使得相同的构建过程可以创建不同的表示的模式。
用于精细化(复杂)的构建一个对象或类
vue2.0,类由很多子类构建而成的
最终暴露出去的是接口、类
适用范围:比较适用与那些有固定生成顺序的对象,或者对象内部有复杂结构的情况
与工厂模式的区别:
工厂模式根据需求的不同,返回不同类的对象,比较灵活
建造者模式返回的是内在逻辑复杂的封装好的对象
设计流程如下:
- 客户提出产品需求
- 指挥者根据产品需求,安排建造者完成需求的各个部分
- 建造者完成相应的部分
修建房子实例:
产品需求:修一个房子,房子里需要有卧室,厨房,客厅
指挥者设计设计图,要求工人施工
工人修建房子
//产品类:产品要素
class House{
constructor(){
this.need = ['卧室', '厨房', '客厅'];
}
}
//设计图绘制
class Diagram {
constructor() {
console.log('拿到图纸')
}
build(partName) {
console.log(`观察${ partName}图纸`);
}
}
//工人实现设计图
class CreatDiagram extends Diagram {
constructor() {
super();
}
build(partName) {
super.build(partName);
this.worker = new worker(partName);
}
getResult() {
console.log('完工');
return this.worker;
}
}
//工人类:内置施工方法
class worker {
constructor(material) {
this.data = material;
this.make();
}
make(){
console.log(`我开始修建${ this.data}`)
}
}
//指挥官类
class Developer {
construct() {
let house= new House();
let workerOk=house.need.map(el=>{
let builder = new CreatDiagram();
builder.build(el);
return builder.getResult();
})
}
}
// 要求产品
let home = new Developer();
// 生成产品
home.construct();
结果:
优点:
- 低耦合性,复杂的流程简单化,各个类各司其职
- 可扩展性,以后新增需求,只需要在对应类上处理即可,不需要修改其他代码
缺点:
- 内在逻辑越复杂的需求,需要创建的类也越多,会显得代码较为臃肿
构造函数模式
通过构造函数的形式定义类,通过new新增实例
实例:
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person;
printName: function(){
console.log(this.name);
},
printAge: function(){
console.log(this.age);
}
}
var person = new Person('star', 22);
混合模式
将构造函数的引用属性和方法放到其原型上,子类是父类原型的一个实例。
实例:
function Person(name,age){
this.name = name;
this.age = age;
};
Person.prototype.printName = function(){
console.log(this.name);
}
function Student(name,age){
//继承 Person 的属性
Person.call(this,name,age);
}
function create(prototype){
function F(){ };
F.prototype = prototype;
return new F();
}
//让Student的原型指向一个对象,该对象的原型指向了Person.prototype,通过这种方式继承 Person 的方法
Student.prototype = create(Person.prototype);
Student.prototype.printAge = function(){
console.log(this.age);
}
var student = new Student('star',22);
student.printName();//star
模块间的沟通
观察者模式(订阅发布模式)
一个订阅者订阅发布者,当一个特定的事件发生的时候,发布者会通知(调用)所有的订阅者。对象之间为一对多的依赖关系。
实例:事件监听(onclick)、vue中的数据驱动等
实例模型:
var EventCenter = (function(){
//将所有的"发布-订阅"关系放到events中
var events = { };
//给事件绑定事件处理程序
//event:事件名
//handler:事件处理程序
function on(event, handler){
events[event] = events[event] || [];
events[event].push({
handler: handler
});
}
//发布消息(触发事件),并执行相应的事件处理程序
//event:事件名
//args:给事件处理程序传递的参数
function fire(event, args){
if (!events[event]) { return}
//遍历事件处理程序列表,执行其中每一个事件处理程序
for (var i = 0; i < events[event].length; i++) {
events[event][i].handler(args);
}
}
//移除
function off(event){
delete events[event];
}
//使用模块模式的方式,向外界提供绑定事件处理程序和触发事件的接口
return {
on: on,
fire: fire,
off: off
}
})();
Event.on('change', function(val){
console.log('change... now val is ' + val);
});
Event.on('click', function(val){
console.log('click.... now val is '+ val);
})
Event.fire('change', 'aaa');
Event.fire('click', 'aaa');
Event.off('change');
职责链模式
让功能的完成以消息按链条传递来处理(线性处理,同步模块)
基本架子
var arr = [同步模块]
var result;
async function run(){
while(arr.length>0){
result = await arr.shift()(result)
}
}
run.then(res=>{
})
结构型设计模式
代理模式
为其他对象提供一种代理,也就是当其他对象直接访问该对象时,如果开销较大,就可以通过这个代理层控制对该对象的访问。
使用场景:懒加载,合并http请求和缓存等
实例1:图片懒加载
// 创建一个本体对象
var myImage = (function(){
// 创建标签
var imgNode = document.createElement( 'img' );
// 添加到页面
document.body.appendChild( imgNode );
return {
// 设置图片的src
setSrc: function( src ){
// 更改src
imgNode.src = src;
}
}
})();
// 创建代理对象
var proxyImage = (function(){
// 创建一个新的img标签
var img = new Image;
// img 加载完成事件
img.onload = function(){
// 调用 myImage 替换src方法
myImage.setSrc( this.src );
}
return {
// 代理设置地址
setSrc: function( src ){
// 预加载 loading
myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
// 赋值正常图片地址
img.src = src;
}
}
})();
proxyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
实例2:请求缓存
var proxyAjax = (
function () {
var caches = { };
return function (param, callback) {
if (caches[param]) { //缓存中有直接返回,不再发送请求
callback(caches[param]);
return;
}
// 模拟异步请求
sendData(param, function (data) {
caches[param] = data;
callback(data);
});
}
}
)();
function sendData(param, callback) {
//模拟请求返回
setTimeout(function () {
callback(param + Math.random());
}, 1000);
}
//发起请求
proxyAjax(param,function(){
})
性能优化(质量调优)
策略模式
定义一系列算法,将他们封装起来,让算法的使用和算法的实现分离开来,避免多重判断调用哪些算法。
使用场景:适用于有多个判断分支的场景,如较多的if、else,解决表单验证的问题
一个基于策略模式的程序至少由两部分组成:一组策略和一个调用环境
实例1:发放奖金,绩效得A的员工,将拿到原本工资3倍的钱;绩效得B的员工,将拿到原本工资2倍的钱;绩效得C的员工,将拿到原本工资*1.5倍的钱
//一组策略
var strategies = {
"A": function(original) {
return original*3
},
"B": function(original) {
return original*2
},
"C": function(original) {
return original*1.5
}
}
//调用环境
function calcSalary(original, grade) {
return strategies[grade](original)
}
calcSalary(2000,'A') // 6000
calcSalary(2000,'B') // 4000
calcSalary(2000,'C') // 3000
实例2:表单验证,用户名不能为空,密码长度要大于6位,当提交表单的时候对表单做验证
//一组策略
var strategies = {
isNotEmpty: function(value, msg) {
if(value === '') {
return msg
}
},
minLength: function(value, length, msg) {
if(value.length < length) {
return msg
}
}
}
//调用环境封装
var Validator = function(){
this.cache = []
};
Validator.prototype.add = function(dom, rule, msg) {
var ruleKey = [];
var ruleVal = ''
if(rule.indexOf(':')!=-1) {
ruleKey = rule.split(':')[0];
ruleVal = rule.split(':')[1];
} else {
ruleKey = rule;
}
this.cache.push(function(){
var temp=[]
temp.push(dom.value)
if(ruleVal) {
temp.push(ruleVal)
}
temp.push(msg)
return strategies[ruleKey].apply(dom, temp);
})
}
//不用区分具体的表单类型,总是会返回同样的结果——一个没有通过验证的列表和错误信息
Validator.prototype.validate = function() {
var count = 0
this.cache.forEach(function(item){
var msg = item()
if(msg) { //存在错误
count++
alert(msg)
}
})
if(count > 0) {
return false
} else {
return true
}
};
//创建一个Validator实例,在表单提交事件中调用validate方法
var test = new Validator()
test.add(registerForm.username, 'isNotEmpty', '用户名不能为空');
test.add(registerForm.password, 'minLength: 6','密码至少6位');
registerForm.onsubmit = function() {
if(!test.validate()) {
return false;
}
}
享元模式
对数据、方法共享分离,减少重复对象创建,降低内存消耗。
享元模式要求将对象的属性划分为内部状态与外部状态(状态在这里通常指属性)
内部与外部状态的划分
- 内部状态存储于对象内部
- 内部状态可以被一些对象共享
- 内部状态独立于具体的场景,通常不会改变
- 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
有多少种内部状态的组合,系统中便最多存在多少个共享对象,外部状态存储于共享对象外部。在需要用到的时候,外部状态传入与共享对象组成一个完整的对象。
适用场景:
- 一个程序中使用了大量的相似对象
- 由于使用了大量对象,造成很大的内存开销
- 对象的大多数状态都可以变为外部状态
- 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象
实例:
多个文件上传(转载:https://www.cnblogs.com/xiaohuochai/p/8039957.html)
区分内外部状态
内部状态:上传方式
外部状态:文件名称、文件大小
var Upload = function( uploadType){
this.uploadType = uploadType;
};
//文件删除处理
Upload.prototype.delFile = function( id ){
uploadManager.setExternalState( id, this );
if ( this.fileSize < 3000 ){
return this.dom.parentNode.removeChild( this.dom );
}
if ( window.confirm( '确定要删除该文件吗? ' + this.fileName ) ){
return this.dom.parentNode.removeChild( this.dom );
}
}
//创建upload对象
var UploadFactory = (function(){
var createdFlyWeightObjs = { };
return {
create: function( uploadType){
//如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象
if ( createdFlyWeightObjs [ uploadType] ){
return createdFlyWeightObjs [ uploadType];
}
return createdFlyWeightObjs [ uploadType] = new Upload( uploadType);
}
}
})();
//创建uploadManager对象,它负责向UploadFactory提交创建对象的请求,并用一个uploadDatabase对象保存所有upload对象的外部状态,以便在程序运行过程中给upload共享对象设置外部状态
var uploadManager = (function(){
var uploadDatabase = { };
return {
add: function( id, uploadType, fileName, fileSize ){
//仅创建了2个对象,即两个内部状态共享对象
var flyWeightObj = UploadFactory.create( uploadType );
var dom = document.createElement( 'div' );
dom.innerHTML =
'<span>文件名称:'+ fileName +', 文件大小: '+ fileSize +'</span>' +
'<button class="delFile">删除</button>';
dom.querySelector( '.delFile' ).onclick = function(){
flyWeightObj.delFile( id );
}
document.body.appendChild( dom );
uploadDatabase[ id ] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj ;
},
setExternalState: function( id, flyWeightObj ){
var uploadData = uploadDatabase[ id ];
for ( var i in uploadData ){
flyWeightObj[ i ] = uploadData[ i ];
}
}
}
})();
//触发上传动作的startUpload函数,即内部状态
var id = 0;
window.startUpload = function( uploadType, files ){
for ( var i = 0, file; file = files[ i++ ]; ){
var uploadObj = uploadManager.add( ++id, uploadType, file.fileName, file.fileSize );
}
};
//上传文件,plugin和flash为uploadType
startUpload( 'plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
{
fileName: '2.html',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload( 'flash', [
{
fileName: '4.txt',
fileSize: 1000
},
{
fileName: '5.html',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 5000
}
]);
结果显示:
模块模式
模块模式可以指定类想暴露的属性和方法,不会污染全局,采用闭包的形式。
实例:
var Person = (function() {
var name = 'xxx'
function sayName() {
console.log(name)
}
return{
name: name,
sayName: sayName
}
})()
还没有评论,来说两句吧...