• 前端常用的设计模式


    前言

         设计模式定义:在面向对象软件设计过程中 针对特定问题的简洁而优雅的解决方案。在不同的编程语言中,对设计模式的实现其实是可能会有区别的。

    • 单例模式   
    • 观察者模式
    • 工厂模式
    • 命令模式
    • 职责链模式

       1,单例模式  

           定义:是保证一个类只有一个实例,并且提供一个访问它的全局访问点。

           需求:一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象、登录浮窗等。

           实现:用一个变量标识当前是否已经为某个类创建过对象,如果是,则在下一次获取这个类的实例时,直接返回之前创建的对象。

           优点:

    • 可以用来划分命名空间,减少全局变量的数量
    • 可以被实例化,且实例化一次,再次实例化生成的也是第一个实例

          基础栗子:

    // 单例模式
    var Singleton = function(name){
        this.name = name;
        this.instance = null;
    };
    Singleton.prototype.getName = function(){
        return this.name;
    };
    // 获取实例对象
    Singleton.getInstance = function(name) {
        if(!this.instance) {
            this.instance = new Singleton(name);
        }
        return this.instance;
    };
    // 测试单例模式的实例
    var a = Singleton.getInstance("aa");
    var b = Singleton.getInstance("bb");
    
    console.log(a===b)    // true

       实践栗子

    (function () {
        //管理单例的逻辑代码,如果没有数据则创建,有数据则返回
       var getSingle = function(fn){ //参数为创建对象的方法
           var result;
           return function(){ //判断是Null或赋值
               return result || (result = fn.apply(this,arguments));
           };
       };
        //创建登录窗口方法
        var createLoginLayer = function(){
            var div = document.createElement('div');
            div.innerHTML = '我是登录浮窗';
            div.style.display = 'none';
            document.body.appendChild(div);
            return div;
        };
        //单例方法
        var createSingleLoginLayer = getSingle(createLoginLayer);
    
        //使用惰性单例,进行创建
        document.getElementById('loginBtn').onclick = function(){
            var loginLayer = createSingleLoginLayer();
            loginLayer.style.display = 'block';
        };
    })()

      2,观察者模式

         定义:对象间的一种一对多的依赖关系。

          需求:当一个对象的状态发生变化时,所有依赖于他的对象都将得到通知。

          优点:时间上的解耦,对象之间的解耦。

          实现:

    1. 首先,指定好谁充当发布者;    
    2. 然后,给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者; 
    3. 最后,发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数。

         基础栗子:

    var salesOffices = {};                           // 定义售楼处
    salesOffices.clientList = [];                    // 缓存列表,存放订阅者的回调函数
    salesOffices.listen = function( fn ){            // 增加订阅者
        this.clientList.push( fn );                  // 订阅的消息添加进缓存列表
    };
    salesOffices.trigger = function(){               // 发布消息
        for( var i = 0, fn; fn = this.clientList[ i++ ]; ){
            fn.apply( this, arguments );             // arguments 是发布消息时带上的参数
        }
    };
    //调用
    salesOffices.listen( function( price, squareMeter ){//订阅消息
        console.log( '价格= ' + price );
        console.log( 'squareMeter= ' + squareMeter );
    });
    salesOffices.trigger( 2000000, 88 );                // 输出:200 万,88 平方米

       实践栗子:登录页面登录后,会需要刷新各个模块的信息(头像、nav)这类。

    var ObserverEvent = (function () {
            var clientList = [], listen, trigger, remove;
            listen = function (key, fn) {
                if (!clientList[key]) {
                    clientList[key] = [];
                }
                clientList[key].push(fn);
            };
            trigger = function () {
                var key = Array.prototype.shift.call(arguments), fns = clientList[key];
                if (!fns || fns.length === 0) {
                    return false;
                }
                for (var i = 0, fn; fn = fns[i++];) {
                    fn.apply(this, arguments);
                }
            };
            remove = function (key, fn) {
                var fns = clientList[key];
                if (!fns) {
                    return false;
                }
                if (!fn) {
                    fns && (fns.length = 0);
                } else {
                    for (var l = fns.length - 1; l >= 0; l--) {
                        var _fn = fns[l];
                        if (_fn === fn) {
                            fns.splice(l, 1);
                        }
                    }
                }
            };
            return {
                listen:listen,
                trigger:trigger,
                remove:remove
            }
        })();
        ObserverEvent.listen('squareMeter88', fn1 = function (price) {
            console.log('价格=' + price);
        });
        ObserverEvent.listen('squareMeter100', function (price) {
            console.log('价格=' + price);
        });
        ObserverEvent.trigger('squareMeter88', 200000);
    
    //刷新模块信息
    var header = (function () {
            ObserverEvent.listen('loginSucc', function (data) {
                header.setAvatar(data.avatar);
            });
            return {
                setAvatar: function (data) {
                    console.log(data + "设置header成功");
                }
            }
        })();
        var nav = (function () {
            ObserverEvent.listen('loginSucc', function (data) {
                nav.setAvatar(data.avatar)
            });
            return {
                setAvatar: function (data) {
                    console.log(data + '设置nav成功');
                }
            }
        })();
        var data = {};
        data.avatar = "参数";
        ObserverEvent.trigger('loginSucc', data);

    3,工厂模式:

        定义:将其成员对象的实例化推迟到子类来实现的类。

         需求:创建对象的流程赋值的时候,比如依赖于很多设置文件等 ;处理大量具有相同属性的小对象;注:不能滥用

         优点:不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中。     

         分类:简单工厂,工厂方法和抽象工厂。

         实现:

            3.1  简单工厂模式 (创建单一对象,需要的类比较少)

    let UserFactory = function (role) {
      function SuperAdmin() {
        this.name = "超级管理员",
        this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理']
      }
      function Admin() {
        this.name = "管理员",
        this.viewPage = ['首页', '通讯录', '发现页', '应用数据']
      }
      function NormalUser() {
        this.name = '普通用户',
        this.viewPage = ['首页', '通讯录', '发现页']
      }
    
      switch (role) {
        case 'superAdmin':
          return new SuperAdmin();
          break;
        case 'admin':
          return new Admin();
          break;
        case 'user':
          return new NormalUser();
          break;
        default:
          throw new Error('参数错误, 可选参数:superAdmin、admin、user');
      }
    }

          3.2  工厂方法模式 (创建多类对象,需要的类比较多)

                 为方便后续新增类方便,只需改一处代码,封装了工厂方法而已。并且把类都放在工厂类原型中实现。

    //安全模式创建的工厂方法函数
    let UserFactory = function(role) {
      if(this instanceof UserFactory) {
        var s = new this[role]();
        return s;
      } else {
        return new UserFactory(role);
      }
    }
    
    //工厂方法函数的原型中设置所有对象的构造函数
    UserFactory.prototype = {
      SuperAdmin: function() {
        this.name = "超级管理员",
        this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理']
      },
      Admin: function() {
        this.name = "管理员",
        this.viewPage = ['首页', '通讯录', '发现页', '应用数据']
      },
      NormalUser: function() {
        this.name = '普通用户',
        this.viewPage = ['首页', '通讯录', '发现页']
      }
    }
    
    //调用
    let superAdmin = UserFactory('SuperAdmin');
    let admin = UserFactory('Admin') 
    let normalUser = UserFactory('NormalUser')

         3.3  抽象工厂模式 (创建父类,子类继承父类,具体实现在子类)

             抽象工厂其实是实现子类继承父类的方法,只是一个方法。

             抽象工厂模式一般用在多人协作的超大型项目中,并且严格的要求项目以面向对象的思想进行完成。

    // 抽象工厂方法
    var VehicleFatory = function(subType, superType) {
        // 判断抽象工厂中是否有该抽象类
        if(typeof VehicleFactory[superType] === 'function') {
            // 缓存类
            function F() {};
            // 继承父类属性和方法
            F.prototype = new VehicleFactory[superType] ();
            // 将子类constructor 指向子类
            subType.constructor = subType;
            // 子类原型继承'父类'
            subType.prototype = new F();
        } else {
            // 不存在该抽象类抛出错误
            throw new Error('未创建该抽象类');
        }
    };
    
    // 小汽车抽象类
    VehicleFactory.Car = function() {
        this.type = 'car';
    };
    VehicleFactory.Car.prototype = {
        getPrice: function() { return new Error('抽象方法不能调用'); },
        getSpeed: function() { return new Error('抽象方法不能调用'); }
    };
    
    // 公交车抽象类
    VehicleFactory.Bus = function() {
        this.type = 'bus';
    };
    VehicleFactory.Bus.prototype = {
        getPrice: function() { return new Error('抽象方法不能调用'); },
        getSpeed: function() { return new Error('抽象方法不能调用'); }
    };
    
    // 货车抽象类
    VehicleFactory.Truck = function() {
        this.type = 'truck';
    };
    VehicleFactory.Truck.prototype = {
        getPrice: function() { return new Error('抽象方法不能调用'); },
        getSpeed: function() { return new Error('抽象方法不能调用'); }
    };
    
    // 创建产品子类继承相应的产品簇抽象类
    // 宝马汽车子类
    var BMW = function(price, speed) {
        this.price = price;
        this.speed = speed;
    }
    //抽象工厂实现对Car抽象类的继承
    VehicleFactory(BMW, 'Car');
    BMW.prototype.getPrice = function() { return this.price };
    BMW.prototype.getSpeed = function() { return this.speed };
    
    // 公交车...
    // 货车...

    4,命令模式:

         定义:用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行。

           需求:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

          实现:将函数的调用、请求和操作封装成一个单一的对象。

     1 var setCommand = function(button,func) {
     2     button.onclick = function(){
     3         func();
     4     }
     5  }; 
     6  var MenuBar = {
     7     refersh: function(){
     8         alert("刷新菜单界面");
     9     }
    10  };
    11  var SubMenu = {
    12     add: function(){
    13         alert("增加菜单");
    14     }
    15  };
    16  // 刷新菜单
    17  var RefreshMenuBarCommand = function(receiver) {
    18     return function(){
    19         receiver.refersh();    
    20     };
    21  };
    22  // 增加菜单
    23  var AddSubMenuCommand = function(receiver) {
    24     return function(){
    25         receiver.add();    
    26     };
    27  };
    28  var refershMenuBarCommand = RefreshMenuBarCommand(MenuBar);
    29  // 增加菜单
    30  var addSubMenuCommand = AddSubMenuCommand(SubMenu);
    31  setCommand(b1,refershMenuBarCommand);
    32 
    33  setCommand(b2,addSubMenuCommand);

    5,职责链模式:

          定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。(大函数分割成一个个小函数,清晰,各司其职)

          需求:代码不清晰,可读性差,拆分函数。

          实现:

    //----------------------改造前---------------
    var order = function( orderType, pay, stock ){ if ( orderType === 1 ){ // 500 元定金购买模式 if ( pay === true ){ // 已支付定金 console.log( '500 元定金预购, 得到 100 优惠券' ); }else{ // 未支付定金,降级到普通购买模式 if ( stock > 0 ){ // 用于普通购买的手机还有库存 console.log( '普通购买, 无优惠券' ); }else{ console.log( '手机库存不足' ); } } } else if ( orderType === 2 ){ // 200 元定金购买模式 if ( pay === true ){ console.log( '200 元定金预购, 得到 50 优惠券' ); }else{ if ( stock > 0 ){ console.log( '普通购买, 无优惠券' ); }else{ console.log( '手机库存不足' ); } } } else if ( orderType === 3 ){ if ( stock > 0 ){ console.log( '普通购买, 无优惠券' ); }else{ console.log( '手机库存不足' ); } } }; order( 1 , true, 500); // 输出: 500 元定金预购, 得到 100 优惠券
    //--------------------- 改造后----------------------------
    // 500 元订单 var order500 = function( orderType, pay, stock ){ if ( orderType === 1 && pay === true ){ console.log( '500 元定金预购, 得到 100 优惠券' ); }else{ order200( orderType, pay, stock ); // 将请求传递给 200 元订单 } }; // 200 元订单 var order200 = function( orderType, pay, stock ){ if ( orderType === 2 && pay === true ){ console.log( '200 元定金预购, 得到 50 优惠券' ); }else{ orderNormal( orderType, pay, stock ); // 将请求传递给普通订单 } }; // 普通购买订单 var orderNormal = function( orderType, pay, stock ){ if ( stock > 0 ){ console.log( '普通购买, 无优惠券' ); }else{ console.log( '手机库存不足' ); } }; // 测试结果: order500( 1 , true, 500); // 输出:500 元定金预购, 得到 100 优惠券 order500( 1, false, 500 ); // 输出:普通购买, 无优惠券 order500( 2, true, 500 ); // 输出:200 元定金预购, 得到 500 优惠券 order500( 3, false, 500 ); // 输出:普通购买, 无优惠券 order500( 3, false, 0 ); // 输出:手机库存不足

     精简版:前端中的常用设计模式

    https://www.cnblogs.com/whaleAlice/p/11900997.html

  • 相关阅读:
    SVN导入maven项目
    jQuery (二)DOM 操作
    jQuery (一)选择器
    jQuery 初识
    mysql的OFFSET实现分页
    MongoDB学习之(三)增删查改
    java安全性的一种简单思路
    java Spring 事务的初次使用与验证
    C#语法之匿名函数和Lambda表达式
    C#语法之委托和事件
  • 原文地址:https://www.cnblogs.com/TigerZhang-home/p/13272218.html
Copyright © 2020-2023  润新知