CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础原理及工具方法

    • JavaScript知识点
    • Javascript 事件流
    • 设计模式应用
      • 单例模式
      • 发布订阅模式
      • 配合发布订阅模式的商品抢购计时器
      • 观察者模式和订阅-发布模式的区别,各自适用于什么场景
      • 设计模式六大原则
        • OCP 原则
        • SRP 原则
    • 原型和继承
    • PromiseA+规范的实现
    • 前端模块化及进程
    • 前端性能异常监控
    • 工作用得到的一些JS方法
    • 项目代码层面优化方法
  • Babel

  • WebComponent

  • 专项知识汇总

  • JavaScript笔记
  • 基础原理及工具方法
CD
2020-01-03
目录

设计模式应用

# 单例模式

var proxySingleton = (function() {
  var singer = null;
  return function(Func) {
    if (!singer) {
      singer = new Func();
    }
    return singer;
  };
})();

class Singleton {
  constructor(name) {
    this.name = name;
    this.instance = null;
  }
  static getInstance(name) {
    if (!this.instance) {
      this.instance = new Singleton(name);
    }
    return this.instance;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 发布订阅模式

export enum EventName {
  HandleEvent = "HandleEvent",
}

export interface EventCallbackMap {
  [EventName.HandleEvent]: [];
}

export class EventEmitter<T extends EventName = any> {
  eventsStore: Map<any, any>;

  constructor() {
    this.eventsStore = new Map(); // 储存{事件:回调}键值对
  }

  emit(type: T, ...args: EventCallbackMap[T]) {
    console.log("emit ", type);
    //从存储的Map键值对this._events中获取对应事件回调函数
    const handler = this.eventsStore.get(type);
    if (!handler) {
      return false;
    }
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        if (args.length > 0) {
          handler[i].apply(this, args);
        } else {
          handler[i].call(this);
        }
      }
    } else {
      if (args.length > 0) {
        handler.apply(this, args);
      } else {
        handler.call(this);
      }
    }

    return true;
  }

  on(type: T, fn: (...args: EventCallbackMap[T]) => void) {
    console.log("on ", type);

    const handler = this.eventsStore.get(type);

    if (!handler) {
      this.eventsStore.set(type, fn);
    } else if (handler && typeof handler === "function") {
      //hanlder为函数,说明只有一个监听者
      //多个监听者用数组存储
      this.eventsStore.set(type, [handler, fn]);
    } else {
      //已有多个监听者,直接Push进数组
      handler.push(fn);
    }
  }

  delete(type: T, fn: (...args: EventCallbackMap[T]) => void) {
    const handler = this.eventsStore.get(type);
    // 如果是函数,说明只被监听了一次
    if (handler && typeof handler === "function") {
      this.eventsStore.delete(type);
    } else {
      let postion;
      for (let i = 0; i < handler.length; i++) {
        if (handler[i] === fn) {
          postion = i;
        } else {
          postion = -1;
        }
      }
      if (postion !== -1) {
        handler.splice(postion, 1);
        if (handler.length === 1) {
          this.eventsStore.set(type, handler[0]);
        }
      } else {
        return this;
      }
    }
  }
}
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
73
74
75
76
77
78
79
80
81
82
83

# 配合发布订阅模式的商品抢购计时器

enum CountdownStatus {
    opening,
    stoped,
    paused
}

interface CountdownOptions {
    showTwoMillSecond?: boolean;
}

export class Countdown extends EventEmitter {
    private static COUNT_IN_MILLISECOND: number = 1 * 100;
    private static SECOND_IN_MILLISECOND: number =
        10 * Countdown.COUNT_IN_MILLISECOND;
    private static MINUTE_IN_MILLISECOND: number =
        60 * Countdown.SECOND_IN_MILLISECOND;
    private static HOUR_IN_MILLISECOND: number =
        60 * Countdown.MINUTE_IN_MILLISECOND;
    private static DAY_IN_MILLISECOND: number =
        24 * Countdown.HOUR_IN_MILLISECOND;

    private endTime: number;
    private step: number;
    private options: CountdownOptions;
    private remainTime: number = 0;
    private status: CountdownStatus = CountdownStatus.stoped;
    constructor(
        endTime: number,
        step: number = 1e3, // 程序中"1e3" = 1 x 10 ^ 3
        options: CountdownOptions = {}
    ) {
        super();
        this.endTime = endTime;
        this.step = step;
        this.options = options;
    }
    countdown() {
        if (this.status !== CountdownStatus.opening) return;
        // 相差时间值 如果给到的时间本身就小于当前时间,则等于0后不执行
        this.remainTime = Math.max(this.endTime - Date.now(), 0);

        // 每一次执行计时器发生计时触发发布订阅模式中用户添加的方法,返回时间参数
        this.emit("countdown", this.parseRemainTime(this.remainTime));

        if (this.remainTime > 0) {
            setTimeout(() => this.countdown(), this.step);
        } else {
            this.stop();
        }
    }
    stop() {
        this.emit("stop");
        this.status = CountdownStatus.stoped;
    }
    opening() {
        this.emit("opening");
        this.status = CountdownStatus.opening;
    }
    pause() {
        this.emit("pause");
        this.status = CountdownStatus.paused;
    }

    private parseRemainTime(remainTime: number) {
        let time = remainTime;

        const days = Math.floor(time / Countdown.DAY_IN_MILLISECOND);
        time = time % Countdown.DAY_IN_MILLISECOND;

        const hours = Math.floor(time / Countdown.HOUR_IN_MILLISECOND);
        time = time % Countdown.HOUR_IN_MILLISECOND;

        const minutes = Math.floor(time / Countdown.MINUTE_IN_MILLISECOND);
        time = time % Countdown.MINUTE_IN_MILLISECOND;

        const seconds = Math.floor(time / Countdown.SECOND_IN_MILLISECOND);
        time = time % Countdown.SECOND_IN_MILLISECOND;

        let count = time;
        if (!this.options.showTwoMillSecond) {
            count = Math.floor(time / Countdown.COUNT_IN_MILLISECOND);
        }

        return {
            days,
            hours,
            minutes,
            seconds,
            count
        };
    }
}

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

# 观察者模式和订阅-发布模式的区别,各自适用于什么场景

观察者模式中的主题是相互感知的,发布订阅模式中的主题是相互不感知的,是借助第三方来发布调度的。

# 设计模式六大原则

# OCP 原则

OCP Open Closed Principle (开闭原则)。软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。

如何遵循抽象约束:

a) 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中的不存在的 public 方法;

b) 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;

c) 抽象层尽量保持稳定,一旦确定即不允许修改。

封装变化:

a) 将相同的变化封装到一个接口或抽象类中;

b) 将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一接口或抽象类中。(23 设计模式也是从各个不同的角度对变化进行封装的)

最佳实践: 封装变化:按可能变化的不同去封装变化; 抽象约束:抽象层尽量保持稳定,一旦确定即不允许修改。

例如,我去买书,买海绵宝宝的会提示货物不足, 买派大星的会弹出支付,如果购买的这个物品是极品(JP),则会返回高亮的效果

if (buy === "hmbb") {
  alert("货物不足");
} else {
  // normal
}
if (buy === "pdx") {
  payfor("10000");
} else {
  // normal
}

// 如果有极品
if (buy === "hmbb") {
  alert("货物不足");
} else if (buy === "jp") {
  // 高亮
} else {
  // normal
}

//OCP原则重构

class Mall {
  constructor(buy) {
    this.buy = buy;
  }
  setHighLight() {
    // 正常
  }
  openDialog() {
    // 正常
  }
}
class HMBB extends Mall {
  openDialog() {
    // 货物不足弹窗
  }
}
class PDX extends Mall {
  openDialog() {
    // payfor弹窗
  }
}
class JP extends Mall {
  setHighLight() {
    // 高亮
  }
}
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

# SRP 原则

SRP -单一职责原则

  • 目标: 一个模块只能做一件事, 通过解耦让模块更加独立
// 需求: game store 设计开发结算模块
class PUBGManager {
  // 弹出结算框
  openDialog() {
    // 计算金额
    setPrice();
  }
}

const game = new PUBGManager();
game.openDialog(); // 弹窗 < = > setPrice 耦合

// 重构
// 拆分计算金额和结算弹框
// gameManager.js
class PUBGManager {
  constructor(command) {
    this.command = command;
  }
  openDialog(price) {
    // 计算金额
    this.command.setPrice(price);
  }
}

class PriceManager {
  setPrice(price) {
    // 计算金额
  }
}

const exe = new PriceManager();
const game = new PUBGManager(exe);
game.openDialog(15);
exe.setPrice(10);
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
编辑 (opens new window)
#JavaScript
上次更新: 2023/03/22, 15:28:00
Javascript 事件流
原型和继承

← Javascript 事件流 原型和继承→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式