设计模式

装饰器模式

  • 在不改变原有的结构和功能 为对象添加功能
  • 装饰模式有时候比继承更加灵活

在不改变原有的结构和功能 为对象添加功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Duck{
eat(food) {
console.log(`吃${food}`);
}
}

class TangDuck{
constructor() {
this.duck=new Duck();
}
eat(food) {
this.duck.eat(food);
console.log('谢谢');
}
}
let t = new TangDuck();
t.eat('苹果');

打印: 吃苹果 谢谢

解析:Duck原组件没有改变,TangDuck对它做了增强,除了原有的之外添加了额外的逻辑

装饰模式有时候比继承更加灵活

装饰器模式是将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链条依次传递到所有的对象,每个对象有处理这个请求的机会。

下面是链式的水+咖啡+奶+糖=13的示例

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
class Coffee{
make(water){
return `${water}+咖啡`;
}
cost(){
return 10;
}
}

class MilkCoffee{
constructor(parent){
this.parent = parent;
}
make(water){
return `${this.parent.make(water)}+牛奶`;
}
cost(){
return this.parent.cost()+1;
}
}

class SugerCoffee{
constructor(parent){
this.parent = parent;
}
make(water){
return `${this.parent.make(water)}+糖`;
}
cost(){
return this.parent.cost()+2;
}
}
let coffee = new Coffee();
let milkCoffee = new MilkCoffee(coffee);
let milksugerCoffee = new SugerCoffee(milkCoffee);
console.log(milksugerCoffee.make('水')+'='+milksugerCoffee.cost());

AOP

面向切面编程
就是在函数执行之前或之后添加一些额外的逻辑,而不需要函数的功能。

应用场景

埋点

埋点分析,是网站分析的一种常用的数据采集方法

埋点方式

  • 服务器层面的:主要是通过客户端的请求进行分析
  • 客户端层面的:通过埋点进行相应的分析
    • 代码埋点
    • 自动化埋点:通过AOP思想对相应的方法进行统计
    • 第三方实现 百度、友盟等…
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
<body>
<button data-name="西瓜" id="watermelon">西瓜</button>
<button data-name="苹果" id="apple">苹果</button>
<script>
let watermelon = document.getElementById('watermelon');
let apple = document.getElementById('apple');
Function.prototype.after = function(afterFn){
let _this = this;
return function(){
_this.apply(this,arguments); // 先执行15-17
afterFn.apply(this,arguments); // 再执行19-22
}
}
// 这是正常逻辑
function click(){
console.log('点击'+this.dataset.name);
}
// 这是埋点逻辑
click = click.after(function(){
let img = new Image();
img.src = `http://localhost:3000?name=${this.dataset.name}`; // 当前按钮点击的名字通过src发送给服务器端
});
Array.from(document.querySelectorAll('button')).forEach(function(button){ // 给每个button上都增加一个事件
button.addEventListener('click',click);
});
1
2
3
4
5
6
7
let express = require('express');
let app = express();
app.get('/',function(req,res){
console.log('name',req.query.name);
res.end('ok');
});
app.listen(3000);

表单校验

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
<body>
<form action="">
用户名<input type="text" name="username" id="username">
密码<input type="text" name="password" id="password">
<button id="submit-btn" >提交</button>
</form>
<script>
Function.prototype.before = function(beforeFn){
let _this = this;
return function(){
let ret = beforeFn.apply(this,arguments);
if(ret)
_this.apply(this,arguments);
}
}
function submit(){ // 业务逻辑
alert('提交表单');
}
let checkUserNameNotNull= submit.before(function(){ // 校验逻辑 和 业务逻辑分开
let username = document.getElementById('username').value;
if(username.length<6){
return alert('用户名不能少于6位');
}
return true;
});
let checkUserNameMoreThanSix = checkUserNameNotNull.before(function(){ // 校验逻辑 和 业务逻辑分开
let username = document.getElementById('username').value;
if(!username){
return alert('用户名不能为空');
}
return true;
});
document.getElementById('submit-btn').addEventListener('click',checkUserNameMoreThanSix);

express 不太一样,但是koa和这个原理是一样的
axios的interceptor就是依据这个写的

类装饰器 | 方法装饰器

1
2
3
4
@testable 
class Person{

}

观察者模式

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
class Star{
constructor(name) {
this.name=name;
this.state='';
this.observers=[]; // 观察者数组
}
getState() { // 获取状态
return this.state;
}
setState(state) {// 改变状态
this.state=state;
this.notifyAllObservers();
}
attach(observer) { // 绑定一个新的观察者
this.observers.push(observer);
}
notifyAllObservers() { // 通知所有的观察者更新自己
this.observers.forEach(observer=>observer.update());
}
}
class Fan{
constructor(name,subject) {
this.name=name;
this.subject=subject;
this.subject.attach(this);
}
update() {
console.log(`${this.subject.name}有新的状态-${this.subject.getState()},${this.name}正在更新`);
}
}
let star=new Star('明星');
let fan1=new Fan('粉丝',star);
star.setState('结婚');

观察者模式区别于发布订阅模式:

  • 被观察者和观察者是耦合的(被观察者内部保存观察者的引用地址的数组用来通知观察者的)
  • 观察者的update动作是由被观察者调用的

发布订阅模式

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
class Agent{ // 中介
constructor(){
this._events = {};
}
// on addEventListener
subscribe(type,listener){
let listeners = this._events[type];
if(listeners){
this._events[type].push(listener);
}else{
this._events[type] = [listener];
}
}
// emit
publish(type){
let listeners = this._events[type];
let args = Array.prototype.slice.call(arguments,1);
if(listeners){
listeners.forEach(listener=>listener(...args));
}
}
}
class LandLord{ // 房东
constructor(name){this.name=name;}
lend(agent,area,money){ // 向外出租
agent.publish('house',area,money);
}
}
class Tenant{ // 租房的人
constructor(name){this.name=name;}
rent(agent,area,money){ //租房
agent.subscribe('house',(area,money)=>{
console.log(`我看到中介的新房源了 ${area}${money}元`);
});
}
}

let agent = new Agent();
let t1 = new Tenant('张三');
let t2 = new Tenant('李四');
t1.rent(agent);
t2.rent(agent);

let landLord = new LandLord();
landLord.lend(agent,60,4000); // 发布房源

打印

1
2
我看到中介的新房源了 60平 4000元
我看到中介的新房源了 60平 4000元

应用场景:

微信公众号,你关注了微信公众号,微信公众号就会给粉丝推消息,微信公众号就是一个主题,粉丝就是观察者。
微博,观察者模式的精髓就是,主题持有观察者的引用,所以才能实现 主题发生改变,观察者能响应到主题发生的变化。一个动作发生了改变,其他几个动作也要发生改变。业务场景就这样。