前端常见设计模式

设计模式听起来像很是高端的样子,其实呢,确实很提莫难啊啊啊啊。言归正传,所谓的设计模式也就是一些比较优秀的编程思想。有了这些思想加于利用到实际开发中,可以减少工作量(便于维护)。废话不多说直接进入主题。

工厂模式

工厂模式,顾名思义,就是为了创造对象。也就是一些公共方法???
工厂模式最重要的优点是:可以在父类实现一些相同的方法,而具体要实现的业务逻辑可以放在子类中,通过子类重写父类的方法,去实现自己的业务逻辑,减少冗余代码。

简单工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function CarFactory (brand, price) {
var car = new Object();
car.brand = brand;
car.price = price;
car.getPrice = function () {
return this.price;
}
return car;
}
var car1 = CarFactory("牌子A", 10000);
var car2 = CarFactory("牌子B", 20000);
console.log(JSON.stringify(car1)); // {"brand":"牌子A","price":10000}
console.log(JSON.stringify(car2)); // {"brand":"牌子B","price":20000}
console.log(typeof car1); // object
console.log(typeof car2); // object
console.log(car1 instanceof Object); // true

函数CarFactory接受两个参数brand, price,最终返回一个对象。如果多次调用这个函数,每次将返回一个新的对象,这就跟工厂的生产线一样。

复杂工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function ComplexCarFactory(brand, price) {
this.brand = brand;
this.price = price;
}
ComplexCarFactory.prototype = {
constructor: ComplexCarFactory,
sellCar: function(){
var speed = this.getSpeed(this.brand);
console.log(this.brand + '的车子售价:' + this.price + '元人民币,限速' + speed + '公里每小时');
},
getSpeed : function(brand){
throw new Error("父类是抽象类不能直接调用,需要子类重写该方法");
}
};
var CarChild = function(brand, price) {
this.brand = brand;
this.price = price;
// 继承构造函数父类中的属性和方法
ComplexCarFactory.call(this, brand, price);
};
// 子类继承父类原型方法
CarChild.prototype = Object.create(ComplexCarFactory.prototype);
// CarChild 子类重写父类的方法
CarChild.prototype.getSpeed = function(brand){
var speed = null;
if(brand === '牌子C'){
return 100;
}
return 50;
}
var car3 = new CarChild("牌子C", 3000);
console.log(car3); // CarChild {brand: "牌子C", price: 3000}
console.log(car3.sellCar()); // 牌子C的车子售价:3000元人民币,限速50公里每小时

ComplexCarFactory为父类,CarChild为子类,CarChild继承自ComplexCarFactory。
ComplexCarFactory不在进行对象实例化,只对创建过程中的一般性问题进行处理,ComplexCarFactory就像是Java中的抽象类,必须被子类重写,否则调用ComplexCarFactory的getSpeed方法时就会抛出异常。
CarChild继承自ComplexCarFactory,同时重写了父类的方法,CarChild类实例后的对象之间是相互独立的,具体的业务逻辑会放在子类中进行编写。

单例模式

单例模式思想在于保证一个特定类仅有一个实例,意味着当你第二次使用同一个类创建信对象时,应得到和第一次创建对象完全相同。
单例模式在我们平时的应用中用的比较多的,相当于把我们的代码封装在一个起来,只是暴露一个入口,从而避免全部变量的污染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Singleton = function(name){
this.name = name;
};
Singleton.prototype.getName = function(){
return this.name;
}
// 获取实例对象
var getInstance = (function() {
var instance = null;
return function(name) {
if(!instance) {
instance = new Singleton(name);
}
return instance;
}
})();
// 测试单例模式的实例
var a = getInstance("aa");
var b = getInstance("bb");
console.log(b.getName()); // "aa"
console.log(a === b); // true

实现一个单例模式,无非就是使用一个变量来标识该类是否被实例化,如果未被实例化的话,那么我们可以实例化一次,否则的话,直接返回已经被实例化的对象。

模块模式

模块模式是为单例模式添加私有变量和私有方法,并减少全局变量的使用;
使用场景,创建一个对象时,需要进行内部初始化,同时对内部属性跟方法有访问权限限制,就需要使用模块模式了。
如下就是一个模块模式的代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var singleMode = (function(){
// 创建私有变量
var privateNum = 112;
// 创建私有方法
function privateFunc(){},
// 创建公有方法
function publicMethod1(){},
function publicMethod2(){},
// 返回一个对象包含公有方法和属性
return {
publicMethod1: publicMethod1,
publicMethod2: publicMethod2
};
})();

模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,先定义了私有变量和函数,供内部函数使用,然后将一个对象字面量作为函数的值返回,返回的对象字面量中只包含可以公开的属性和方法。这样的话,可以提供外部使用该方法;由于该返回对象中的公有方法是在匿名函数内部定义的,因此它可以访问内部的私有变量和函数。

代理模式

代理模式的优点在于:代理对象可以代替本体对象被实例化,此时本体对象未真正实例化,等到合适时机再实例化。代理模式可以延迟创建开销很大的本体对象,他会把本体的实例化推迟到有方法被调用时。

图片加载

使用图片是非常常见的场景,如果直接给img标签设置src属性,如果图片过大,或网速比较慢,图片在加载过程中会有一段时间的空白,用户体验不好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
// 代理模式
var ProxyImage = (function(){
var img = new Image();
img.onload = function(){
myImage.setSrc(this.src);
};
return {
setSrc: function(src) {
myImage.setSrc("http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif");
img.src = src;
}
}
})();
// 调用方式
ProxyImage.setSrc("https://www.baidu.com/img/bd_logo1.png");

myImage 函数只负责做一件事,创建img元素加入到页面中,其中的加载loading图片交给代理函数ProxyImage 去做,当图片加载成功后,代理函数ProxyImage 会通知及执行myImage 函数的方法,同时当以后不需要代理对象的话,我们直接可以调用本体对象的方法即可。

缓存代理

对第一次运行的结果进行缓存,当再一次运行相同运算的时候,直接从缓存里面取,避免重复运算,如果运算非常复杂的话,对性能很耗费,那么使用缓存对象可以提高性能。以下是一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var mult = function(){
var a = 1;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a = a*arguments[i];
}
return a;
};
// 计算加法
var plus = function(){
var a = 0;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a += arguments[i];
}
return a;
}
// 代理函数
var proxyFunc = function(fn) {
var cache = {}; // 缓存对象
return function(){
var args = Array.prototype.join.call(arguments,',');
if(args in cache) {
return cache[args]; // 使用缓存代理
}
return cache[args] = fn.apply(this,arguments);
}
};
var proxyMult = proxyFunc(mult);
console.log(proxyMult(1,2,3,4)); // 24
console.log(proxyMult(1,2,3,4)); // 缓存取 24

var proxyPlus = proxyFunc(plus);
console.log(proxyPlus(1,2,3,4)); // 10
console.log(proxyPlus(1,2,3,4)); // 缓存取 10

职责链模式

策略模式

一、策略模式可以有效避免很多if条件语句 二、策略模式符合开放-封闭原则,使代码更容易理解和扩展 三、策略模式中的代码可以复用.
表单检验是非常常见的功能。因为涉及到大量的验证规则,使用策略模式会非常便利。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 策略对象
var strategys = {
isNotEmpty: function(value,errorMsg) {
if(value === '') {
return errorMsg;
}
},
// 限制最小长度
minLength: function(value,length,errorMsg) {
if(value.length < length) {
return errorMsg;
}
},
// 手机号码格式
mobileFormat: function(value,errorMsg) {
if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
};
var Validator = function(){
this.cache = []; // 保存效验规则
};
Validator.prototype.add = function(dom,rules) {
var self = this;
for(var i = 0, rule; rule = rules[i++]; ){
(function(rule){
var strategyAry = rule.strategy.split(":");
var errorMsg = rule.errorMsg;
self.cache.push(function(){
var strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategys[strategy].apply(dom,strategyAry);
});
})(rule);
}
};
Validator.prototype.start = function(){
for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {
var msg = validatorFunc(); // 开始效验 并取得效验后的返回信息
if(msg) {
return msg;
}
}
};
// 代码调用
var registerForm = document.getElementById("registerForm");
var validateFunc = function(){
var validator = new Validator(); // 创建一个Validator对象
/* 添加一些效验规则 */
validator.add(registerForm.userName,[
{strategy: 'isNotEmpty',errorMsg:'用户名不能为空'},
{strategy: 'minLength:6',errorMsg:'用户名长度不能小于6位'}
]);
validator.add(registerForm.password,[
{strategy: 'minLength:6',errorMsg:'密码长度不能小于6位'},
]);
validator.add(registerForm.phoneNumber,[
{strategy: 'mobileFormat',errorMsg:'手机号格式不正确'},
]);
var errorMsg = validator.start(); // 获得效验结果
return errorMsg; // 返回效验结果
};
// 点击确定提交
registerForm.onsubmit = function(){
var errorMsg = validateFunc();
if(errorMsg){
alert(errorMsg);
return false;
}
}

发布-订阅模式

发布—订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
eg:假设小🐶看上了一双鞋子,但该鞋子已经断货了,卖家承诺她到货通知。与此同时,小🐷、小🐒也关注了这双鞋子。
在这个场景中,卖家就是发布者,小🐶,🐷,🐒等人都属于订阅者。当鞋子到货时,会依次通知到每个人。
实现:
1.首先要想好谁是发布者(比如上面的卖家)。
2.然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(比如上面的买家收藏了卖家的店铺,卖家通过收藏了该店铺的一个列表名单)。
3.最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var Event = (function(){
var list = {},
listen,
trigger,
remove;
listen = function(key,fn){
if(!list[key]) {
// 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
list[key] = [];
}
list[key].push(fn); // 订阅消息添加到缓存列表
};
trigger = function(){
var key = Array.prototype.shift.call(arguments), // 取出消息类型名称
fns = list[key]; // 取出该消息对应的回调函数的集合
// 如果没有订阅过该消息的话,则返回
if(!fns || fns.length === 0) {
return false;
}
for(var i = 0, fn; fn = fns[i++];) {
fn.apply(this,arguments); // arguments 是发布消息时附送的参数
}
};
remove = function(key,fn){
// 如果key对应的消息没有订阅过的话,则返回
var fns = list[key];
// 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
if(!fns) {
return false;
}
if(!fn) {
fns && (fns.length = 0);
}else {
for(var i = fns.length - 1; i >= 0; i--){
var _fn = fns[i];
if(_fn === fn) {
fns.splice(i,1);// 删除订阅者的回调函数
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
// 测试代码如下:
Event.listen("color",function(size) {
console.log("尺码为:"+size); // 打印出尺码为42
});
Event.trigger("color",42);
赞赏是最好的鼓励!
0%