在这里讲述代理模式、观察者模式、装饰器、职责链模式。
1、代理模式
- 首先通过一小段代码来理解一下什么是代理模式
- 男孩要去给女孩送礼物,但是不好意思直接送,所以就出现了一个代理对象去帮男孩给女孩送礼物。
代码示例
// 代理模式的简单实现
// 声明对象
var Girl = function (name) {
this.name = name;
}
var Boy = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
console.log("hi," + this.girl.name + ",送你礼物:" + gift);
}
}
// 代理对象
var ProxyObj = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
(new Boy(this.girl)).sendGift(gift);
}
}
var girl = new Girl("小芳");
var proxyObj = new ProxyObj(girl);
proxyObj.sendGift("999朵玫瑰")
- 用代理模式可以实现图片懒加载,在一个网页中如果展示的图片很大,展现在页面的时间会比较长,这时候可以使用一张小图片暂时代替大图片展现在页面,等到大图片加载完成之后再将小图片换成大图片。
具体代码实现
// 使用代理模式可以实现图片懒加载
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
var img = new Image(); // 代理对象,先展示等待的小图片,接着负责拉取真是图片
img.onload = function () { // 当真实图片加载完成后触发
imgNode.src = this.src;
}
return {
setSrc: function (src) {
// 先展示等待的小图片
imgNode.src = "https://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif";
// 为了明显的看到效果,这里设置成异步,延迟3秒
var timer = setTimeout(() => {
img.src = src; // 把真实图片地址给代理对象,等到代理对象的图片加载完成之后,将图片给imgNode,让其展示真正的图片
clearTimeout(timer)
}, 3000)
}
}
})()
- 结果展示(这里好像展示不了动图,只有分成两张图片了)
刚开始展示的是小图片
大图片加载完成之后展示大图片
2、观察者模式
- 观察者模式
- 示例:用户先订阅商品,当商家发布商品之后,用户就可以获得商品。
具体实现如下
- 示例:用户先订阅商品,当商家发布商品之后,用户就可以获得商品。
// 发布者
var shopObj = {}
// 商品列表
shopObj.list = []
// 订阅
shopObj.listen = function (key, fn) {
if (!this.list[key]) {
this.list[key] = []
}
this.list[key].push(fn);
}
// 发布消息
shopObj.publish = function (key) {
var fns = this.list[key];
for (let i = 0, fn; fn = fns[i]; i++) {
// 执行订阅的fn
fn.apply(this, arguments);
}
}
// 添加订阅
shopObj.listen("huawei", function (brand, model) {
console.log(brand, model);
})
shopObj.listen('apple', function (brand, model) {
console.log(brand, model);
})
// 商家发布消息
shopObj.publish("huawei", 'p30');
shopObj.publish('apple', 'iphone 11')
- 将代码重构
// 代码重构
var event = {
list: [],
listen: function (key, fn) {
if (!this.list[key]) {
this.list[key] = []
}
this.list[key].push(fn);
},
publish: function (key) {
var fns = this.list[key];
for (let i = 0, fn; fn = fns[i]; i++) {
fn.apply(this, arguments)
}
}
}
// 观察者对象初始化
var initEvent = function (obj) {
// for(let i in event) {
// obj[i] = event[i];
// }
// 也可以使用这种方法复制对象
Object.assign(obj, event)
}
// 发布者
var shopObj2 = {}
initEvent(shopObj2);
// 添加订阅
shopObj2.listen("huawei", function (brand, model) {
console.log(brand, model);
})
shopObj2.listen('apple', function (brand, model) {
console.log(brand, model);
})
// 商家发布消息
shopObj2.publish("huawei", 'p30');
shopObj2.publish('apple', 'iphone 11')
- 结果展示
3、装饰器
- 其实就是相当与Java中的注解。
- 通过一个简单的示例来了解一些装饰器
代码如下
// 装饰器的实现
class Circle {
draw() { //行为
console.log("画一个圆");
}
}
// 使用装饰器添加边框
class Decorator {
constructor(circle) {
this.circle = circle;
}
draw() {
this.circle.draw();
this.setBorderCircle(this.circle); //装饰品方法
}
setBorderCircle(circle) {
console.log("绘制边框");
}
}
var circle = new Circle();
new Decorator(circle).draw();
- 以注解的形式简单实现以下装饰器
具体代码
// 装饰器----注解形式
class Boy {
@run
speak() {
console.log("我能唱歌");
}
}
function run(target,key,descriptor) {
// target表示Boy对象,key=被修饰的方法名,descriptor是speak方法描述对象
console.log(target,key,descriptor);
console.log("我能跑步");
}
var boy = new Boy()
boy.speak()
- 注意:如果直接这样子写的话,会直接报错,要通过安装第三方插件的形式将代码转换为es5的形式。
- 分别下载插件 babel-cli 、babel-preset-env 、babel-plugin-transform-decorators-legacy
- 添加.babelrc文件
内容如下
{
"presets": ["env"],
"plugins": ["transform-decorators-legacy"]
}
- 还需要在package.json文件中添加运行的语句
"scripts": {
"build": "babel src/Boy.js -o build/Boy.js", //指定具体打包的文件
"build-dir": "babel src -d build" // 打包src下的所有文件
},
- 在命令窗口中运行: npm run build,就能得到打包好的文件,然后直接运行打包好的文件,可以得到如下结果:
- 装饰器的应用
代码如下
class Math {
@log(100) // 给方法添加日志输出功能 传递参数
add(a,b) {
return a + b;
}
}
function log(num) {
var _num = num || 0;
return function(target,name,descriptor) {
var oldValue = descriptor.value; //add方法
// 重写
descriptor.value = function(...arg){
arg[0] += _num;
arg[1] += _num;
console.log(`调用${name}参数:`,arg);
return oldValue.apply(target,arg)
}
return descriptor
}
}
var math = new Math();
var res = math.add(1,2);
console.log(res);
- 结果如下
4、职责链模式
- 简单来说就是将任务传递下去,就好像是“击鼓传花”的游戏。
- 通过一个简单的示例了解一下职责链模式
- 例子:充值抽奖
充值达到500元可抽100元,达到20元可抽20元,否则无奖
使用职责链实现 问题:如果再添加一个抽奖活动,则代码又需要重新修改,不符合 开闭原则(对修改关闭,对扩展开放)。 - 具体代码
function order500(orderType,isPay,count) {
if(orderType == 1 && isPay) {
console.log("您中奖了100元");
}else{
order200(orderType,isPay,count)
}
}
function order200(orderType,isPay,count) {
if(orderType == 2 && isPay) {
console.log("您中奖了20元");
}else{
orderNormal(orderType,isPay,count)
}
}
function orderNormal(orderType,isPay,count) {
if(count > 0) {
console.log("您已抽到10元");
}else{
console.log("谢谢参与");
}
}
- 通过职责链模式将代码再次重构一下
具体代码
// 使用职责链重构代码
function order500(orderType,isPay,count) {
if(orderType == 1 && isPay) {
console.log("您中奖了100元");
}else{
return "nextSuccessor"
}
}
function order200(orderType,isPay,count) {
if(orderType == 2 && isPay) {
console.log("您中奖了20元");
}else{
return "nextSuccessor"
}
}
function orderNormal(orderType,isPay,count) {
if(count > 0) {
console.log("您已抽到10元");
}else{
console.log("谢谢参与");
}
}
// 职责链关系对象
function Chain(fn) {
this.fn = fn;
this.successor = null;
}
Chain.prototype.setNextSuccessor = function(successor) {
this.successor = successor;
}
Chain.prototype.passRequest = function() {
var ret = this.fn.apply(this,arguments);
if(ret === "nextSuccessor") {
this.successor.passRequest.apply(this.successor,arguments)
}
}
// 实例化职责链
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
// 把链子串起来
chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);
chainOrder500.passRequest(1,true,500)
chainOrder200.passRequest(2,true,100)
- 在控制台上输出的结果为
总结
这四种模式是在B站学习的过程中看到的,不是特别理解,所以在此做一下笔记。