一、单例模式

什么是单例设计模式

  • 定义: 1、只有一个实例 2、可以访问全局
  • 主要解决的问题: 一个全局使用的类频繁地创建与销毁
  • 什么时候用:控制实例数目,节省系统资源
  • 手段:判断系统是否有这个实例,有则返回,没有创建
  • 优点:对复杂的DOM操作来说,封装,对复杂的操作只操作一次。减少内存开销(频繁创建销毁,管理首页页面缓存),避免多重占用(写文件操作)
  • 缺点:没有接口,不能继承,与单一原则冲突,一个类应该只关心内部逻辑,不关心外面怎么来实例化
  • 使用场景:1、全局缓存 2、弹窗

实现一个单例模式

  1. const singleton = function(name){
  2. this.name = name;
  3. this.instance = null;
  4. }
  5. singleton.prototype.getName = function() {
  6. console.log(this.name)
  7. }
  8. singleton.getInstance = function(name){
  9. if(!this.instance){ // 关键语句
  10. this.instance = new singleton(name)
  11. }
  12. return this.instance
  13. }
  14. // test
  15. const a = singleton.getInstance('a') // 通过getInstance来获取实例
  16. const b = singleton.getInstance('b')
  17. console.log(a,b)
  18. console.log(a == b)
  19. // singleton {name: "a", instance: null}
  20. // singleton {name: "a", instance: null}
  21. // true

弹出层实践

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title>单例设计模式</title>
  8. </head>
  9. <body>
  10. <div>
  11. <button id="loginBtn">登陆</button>
  12. <button id="clearloginBtn">注销</button>
  13. </div>
  14. <!-- 解决的问题 减少实例重复实例化的问题 -->
  15. <script>
  16. // 创建
  17. const createLoginLayer = function () {
  18. console.log("create");
  19. let myDiv = document.createElement("div");
  20. myDiv.innerHTML = "登陆浮框";
  21. document.body.appendChild(myDiv);
  22. return myDiv;
  23. };
  24. // 单例模式和创建解藕
  25. const getSingle = function (fn) {
  26. let result = null;
  27. return function (type) {
  28. if (!result) {
  29. result = fn.apply(this, arguments);
  30. }
  31. return result;
  32. };
  33. };
  34. const createSingleLoginLayer = getSingle(createLoginLayer);
  35. let myAlertDOM;
  36. document.getElementById("loginBtn").onclick = function () {
  37. myAlertDOM = createSingleLoginLayer();
  38. myAlertDOM.style.display = "block";
  39. };
  40. document.getElementById("clearloginBtn").onclick = function () {
  41. myAlertDOM.style.display = "none";
  42. };
  43. </script>
  44. </body>
  45. </html>

二、策略模式

什么是策略模式

  • 定义: 根据不同参数命中不同策略
  • 主要解决的问题: 复杂的if…else 带来的复杂和难以维护
  • 如何操作:通过hash映射不同策略
  • 优点:1、算法可以自由切换 2、避免使用多重调节判断 3、扩展性、复用性好
  • 缺点:1、策略类会增多 2、所有策略都需要对外暴露
  • 使用场景 1、 需要动态根据行为改变运行方法 2、需要对策略进行统一管理

    JS中的策略模式

    根据不同参数获取不同的方法

  1. const rule = {
  2. 'model3': function(money){
  3. return money * 3
  4. },
  5. 'modely': function(money){
  6. return money * 4
  7. }
  8. }
  9. const getModelMoney = function(level,money){
  10. return rule[level](money)
  11. }
  12. getModelMoney('model3',1000) // 3000

在函数为一等公民的JS中,策略模式常常隐藏在高阶函数中(以参数的形式传入某函数,返回该执行结果) , 稍微变化上面的例子

  1. const Model3 = function(money){ return money * 3 }
  2. const ModelY = function(money){ return money * 4 }
  3. const getModelMoney = function(fn,money){ return fn(money)}
  4. getModelMoney(Model3,1000) // 3000
  5. getModelMoney(ModelY,1000) // 4000

三、代理模式

什么是代理模式

  • 定义:为其他对象提供一种代理一控制对这个对象的访问
  • 主要解决:避免直接访问对象带来的一系列问题,如要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者进程外的访问),直接访问会给使用者或者系统结构带来麻烦,可以在访问此对象的时候加上一个对比对象的访问层
  • 何时用:向访问一个对象时做一些控制
  • 如何解决: 增加中间层
  • 优点:1、指责清晰 2、高扩展性 3、智能化
  • 缺点:1、由于增加了代理对象,有些类型的代理模式可能会造成请求速度变慢 2、实现代理模式需要额外工作,有些比较复杂
  • 使用场景:按职责来划分,通常又一下场景
  • 远程代理
  • 虚拟代理
  • 保护代理
  • Cache代理
  • 防火墙代理
  • 同步化代理
  • 注意:和适配器模式区别: 适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口,和装饰器模式的区别:装饰器模式为了增强功能,代理模式是为了加以控制

虚拟代理实现图片预加载

下面的代码运用代理模式实现了图片预加载,可以看到通过代理模式巧妙的将创建图片与预加载逻辑分离,并且在未来如果不需要预加载,只要改成请求本体代替请求代理对象就行

  1. const myImage = (function(){
  2. let imgNode = document.createElement("img");
  3. document.appendChild(imgNode)
  4. return {
  5. setSrc: function(src){
  6. imgNode.src = src
  7. }
  8. }
  9. })()
  10. const proxyImage = (function(){
  11. const img = new Image();
  12. img.onload = function(){ // http 图片加载完毕后才会执行
  13. myImage.setSrc(this.src)
  14. }
  15. return {
  16. setSrc: function(src){
  17. myImage.setSrc('loading.jpg') // 本地loading
  18. img.src = src
  19. }
  20. }
  21. })()
  22. proxyImage.setSrc('http://loaded.jpg')

缓存代理实现乘积计算,当多次执行调用某段代码的时候,就可以使用缓存代理的方式

  1. const mult = function () {
  2. console.log('sss');
  3. let a = 1;
  4. for (let l of arguments) {
  5. a = a * l;
  6. }
  7. return a;
  8. };
  9. // let result = mult(1, 2, 3, 4); // 24
  10. const proxyMult = (function(){
  11. let cache = {};
  12. return function(){
  13. let tag = Array.prototype.join.call(arguments,',')
  14. if(cache[tag]){
  15. return cache[tag]
  16. }
  17. cache[tag] = mult.apply(this,arguments)
  18. return cache[tag]
  19. }
  20. })()

四、迭代器模式

  • 定义: 提供一种方法顺序访问一个聚合对象中各元素,又无需暴露该对象的内部表示
  • 解决:不同的方式来便利整个整合对象
  • 合适使用:遍历一个聚合对象
  • 如何解决: 把元素之间游走的责任交给迭代器,而不是聚合对象
  • 关键代码:定义接口: done,next
  • 优点
    • 支持一下不同的方式遍历一个聚合对象
    • 迭代器简化了聚合类
    • 在同一个聚合上可以有多个遍历
    • 在迭代器模式中增加新的聚合类和迭代器类都很方便,无需修改原有代码
  • 缺点:由于迭代器模式将存储数据和遍历数据的指责分离,增加新的聚合类需要对应增加新的迭代器类,累的个数成对数增加,一定程度上增加了系统的复杂性
  • 使用场景
    • 访问一个聚合对象的内融而无需暴露他的内部表示
    • 需要为聚合对象提供多种遍历方式
    • 为遍历不同的聚合结构提供一个统一的接口
  • 注意是想: 迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器来负责,这样可以既不暴露集合内部结构,又可以让外部代码透明的访问内部的数据

实现一个迭代器

  1. function each(arr,fn){
  2. for(let i=0;i<arr.length;i++){
  3. fn(i,arr[i])
  4. }
  5. }
  6. each([1,2,3],function(i,n){
  7. console.log(i)
  8. console.log(n)
  9. })
  10. const compare = function (arr1, arr2) {
  11. let tag = false;
  12. each(arr1, function (i, n) {
  13. if (arr2[i] !== n) {
  14. tag = true;
  15. }
  16. });
  17. if (!tag) {
  18. console.log("两数组相等");
  19. } else {
  20. console.log("两数组不相等");
  21. }
  22. };
  23. let arr1 = [1, 2, 3],
  24. arr2 = [1, 2, 3];
  25. compare(arr1, arr2);

五、发布订阅模式

什么是发布订阅模式(观察者模式)?

  • 定义:订阅对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并被自动更新
  • 主要解决:一个对象状态改变给其他对象通知的问题,而且考虑到易用和低耦合,保证高度的写作
  • 何时用:一个对象状态发生改变,所有依赖的对象都将得到通知,进行广播通知
  • 如何解决:使用面向对象技术,可以将这种依赖关系弱化
  • 关键代码:对应某个topic用数字存放订阅者
  • 应用实例:

    • 拍卖时候,观察最高的通知其他竞价者
    • vue 收集依赖 在set的时候通知所有依赖更新
    • redux在dispatch的时候更新对象的时候,通知视图更新
  • 优点

    • 观察者和被观察者是抽象耦合的
    • 建立一套触发机制
  • 缺点

    • 如果一个被观察着对象有很多的直接和间接的观察着,将所有观察者都通知,花费很多时间
    • 如果观察者和观察目标之间有循环依赖,可能导致系统崩溃
    • 观察者模式没有响应的机制让观察者知道所观察的目标对象怎么变化,仅仅只是知道了发生变化
  • 使用场景

  • 注意事项
    • 避免循环引用
    • 如果顺序执行,某一观察错误会导致系统卡壳,一般采用异步方式
      1. function pubsub() {
      2. var _pubsub = {}, // 全局对象,即发布订阅对象
      3. _topics = {}, // 回调函数存放的数组
      4. _subUid = 0;
      5. // 发布方法
      6. _pubsub.publish = function(topic){
      7. if(!_topics[topic]){
      8. return false;
      9. }
      10. var args = [].slice.call(arguments,1);
      11. setTimeout(function(){
      12. var subscribers = _topics[topic];
      13. for(var i=0;j=subscribers.length;i<j;i++){
      14. subscribers[i].callback.apply(null,args)
      15. }
      16. })
      17. return true
      18. }
      19. // 订阅方法
      20. _pubsub.subscribe = function(topic,callback) {
      21. if(!_topics[topic]){
      22. _topics[topic] = []
      23. }
      24. var token = (++subUid).toString();
      25. _topics[topic].push({
      26. token:token,
      27. callback: callback
      28. })
      29. return token
      30. }
      31. // 取消订阅
      32. _pusub.unsubscribe = function (token) {
      33. for(var m in _topics) {
      34. if(_topocs[m]) {
      35. for(var i=-; j=_topics[m].length;i<j;i++) {
      36. if(_topic[m][i].token === token) {
      37. _topics[m].splice(i,1);
      38. return token;
      39. }
      40. }
      41. }
      42. }
      43. return false;
      44. };
      45. return {
      46. subscribe: _pubsub.subscribe,
      47. publish: _pubsub.publish,
      48. unsubscribe: _pubsub.unsubscribe
      49. }
      50. }

六、命令模式

将界面代码和功能分别由不同人进行拆分

  1. // 界面需要
  2. const setCommand = function(button,command){
  3. button.onClick = function(){
  4. command()
  5. }
  6. }
  7. // 功能
  8. const command = function (){
  9. console.log('更新菜单...')
  10. }

七、装饰器模式

  • 动态的给一个对象添加一些额外的职责
  • 在不增加子类的情况下扩展类的时候用
  • 优点: 解藕, 不增加子类的情况下扩展类

八、适配器模式

优点: 让两个没有关联的类一起运行 提高类的复用 例如 把老得接口适配成新接口 缺点:过多使用会使系统凌乱和混乱 应该重构

  1. // 老接口
  2. const oldCity = (function(){
  3. return [
  4. {
  5. name:'hangzhou',
  6. id:1
  7. },
  8. {
  9. name:'jinhua',
  10. id:2
  11. }
  12. ]
  13. })()
  14. // 转化成新接口 => { hangzhou:1 , jinhua:2 }
  15. const adaptor = function(oldCity){
  16. let obj = {}
  17. for(let city of oldCity){
  18. obj[city.name] = city.id
  19. }
  20. return obj
  21. }
  22. const newCity = adaptor(oldCity);

参考

JavaScript 常用设计模式