js实现各种设计模式 最全最详细

简单工厂模式

  1. 定义: 又叫静态工厂方法,根据不同参数创建不同对象,并赋予属性和方法
  2. 应用:抽取类相同的属性和方法封装到对象
  3. 实例
 let UserFactory = function (role) {
  function User(opt) {
    // 公共的属性和方法提取出来
    this.name = opt.name;
    this.viewPage = opt.viewPage;
  }
  // 根据不同参数返回不同参数
  switch (role) {
    case 'superAdmin':
      return new User(superAdmin);
      break;
    case 'admin':
      return new User(admin);
      break;
    case 'user':
      return new User(user);
      break;
    default:
      throw new Error('参数错误, 可选参数:superAdmin、admin、user')
  }
}

//调用
let superAdmin = UserFactory('superAdmin');
let admin = UserFactory('admin') 
let normalUser = UserFactory('user')
//最后得到角色,可以调用

工厂方法模式

  1. 定义: 对产品类的抽象使其创建业务主要负责用于创建多类产品的实例
  2. 应用: 创建实例
  3. 代码
function createPerson(name, age) {
    var obj = {};
    obj.name = name;
    obj.age = age;
    obj.writeJs = function () {
        console.log(this.name + 'write js');
    }
    return obj;
}

var p1 = createPerson('mengzhe' , 26);
p1.writeJs();

var p2 = createPerson('iceman' , 25);
p2.writeJs();

抽象工厂模式和工厂模式的区别就是: 前者是通过参数返回不一样的实例,后者是直接new出来相应的实例

单例模式

  1. 定义: 一个实例只能被创建一次
  2. 应用: 提供命名空间
  3. 代码
let singleCase = function(name){
    this.name = name;
};
singleCase.prototype.getName = function(){
    return this.name;
}
// 获取实例对象
let getInstance = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {//相当于一个一次性阀门,只能实例化一次
            instance = new singleCase(name);
        }
        return instance;
    }
})();
// 测试单体模式的实例,所以one===two
let one = getInstance("one");
let two = getInstance("two");   

外观模式

1. 定义: 为子系统中的一组接口提供一个一致的界面

  1. 应用: 简化复杂接口( stopPrevent )
  2. 比方:比如在家要看电影,需要打开音响,再打开投影仪,再打开播放器等等,引入外观角色之后,只需要调用“打开电影设备”方法就可以。外观角色封装了打开投影仪等操作,给使用者提供更容易使用的方法。
  3. 代码
function a(x){
   // do something
}
function b(y){
   // do something
}
function ab( x, y ){
    a(x);
    b(y);
} 

适配器模式

  1. 定义:将一个接口转换成客户端需要的接口而不需要去修改客户端代码,使得不兼容的代码可以一起工作
  2. 应用:适配函数参数
  3. 代码
 // 参数适配模拟eg1:
function add (x1, x2, x3) {
  console.log(x1 + x2 + x3);
}
// 存在一个对象数据
var obj = {
  a: '我',
  b: '很',
  c: '帅'
}
function adapter (o) {
  // 通过适配器函数来调用目的api
  add(o.a, o.b, o.c);
} 
adapter(obj);

装饰者模式

  1. 定义:不改变原对象的基础上,给对象添加属性或方法
  2. 应用:@Form @connect 给类关联上对应的属性
  3. 代码
 //获取事件源
  let input=document.getElementById(input);
  //若事件源已经绑定事件
  if(typeof input.onclick=='function'){
    //缓存事件源原有的回调函数
    let oldClickFn=input.onclick;
    //为事件源定义新事件
    input.onclick=function(){
      //事件源原有回调函数
      oldClickFn();
      //执行事件源新增回调函数
      fn();
    }
  }else{
    //未绑定绑定
    input.onclick=fn;
  }
}

//测试用例
decorator('textInp',function(){
  console.log('文本框执行啦');
})
decorator('btn',function(){
  console.log('按钮执行啦');
})

观察者模式

  1. 作用: 解决类与对象,对象与对象之间的耦合,两个组件之间的通信
  2. 场景: VUE的bus,onclick事件绑定
  3. 代码
let Observer=
  (function(){
    let _message={};
    return {
      //注册接口,
        //1.作用:将订阅者注册的消息推入到消息队列
        //2.参数:所以要传两个参数,消息类型和处理动作,
        //3.消息不存在重新创建,存在将消息推入到执行方法
        
      regist:function(type,fn){
        //如果消息不存在,创建
        if(typeof _message[type]==='undefined'){
          _message[type]=[fn];
        }else{
          //将消息推入到消息的执行动作
          _message[type].push(fn);
        }
      },

      //发布信息接口
        //1.作用:观察这发布消息将所有订阅的消息一次执行
        //2.参数:消息类型和动作执行传递参数
        //3.消息类型参数必须校验
      fire:function(type,args){
        //如果消息没有注册,则返回
        if(!_message[type]) return;
          //定义消息信息
          var events={
            type:type, //消息类型
            args:args||{} //消息携带数据
          },
          i=0,
          len=_message[type].length;
          //遍历消息
          for(;i<len;i++){
            //依次执行注册消息
            _message[type][i].call(this,events);
          }
      },

      //移除信息接口
        //1.作用:将订阅者注销消息从消息队列清除
        //2.参数:消息类型和执行的动作
        //3.消息参数校验
      remove:function(type,fn){
        //如果消息动作队列存在
        if(_message[type] instanceof Array){
          //从最后一个消息动作序遍历
          var i=_message[type].length-1;
          for(;i>=0;i--){
            //如果存在该动作在消息队列中移除
            _message[type][i]===fn&&_message[type].splice(i,1);
          }
        }
      }
    }
  })()

//测试用例
  //1.订阅消息
  Observer.regist('test',function(e){
    console.log(e.type,e.args.msg);
  })

  //2.发布消息
  Observer.fire('test',{msg:'传递参数1'});
  Observer.fire('test',{msg:'传递参数2'});
  Observer.fire('test',{msg:'传递参数3'});

中介者模式

  1. 定义:设置一个中间层,处理对象之间的交互
  2. 代码:
var mediator = (function() {
    var topics = {},
        subUid = -1;
    var publish = function(topic, args) {
        if (!topics[topic]) {
            return false;
        }

        var subscribers = topics[topic],
            len = subscribers ? subscribers.length : 0;
        while (len--) {
            subscribers[len].func(topic, args);
        }

        return true;
    };

    var subscribe = function(topic, func) {
        if (!topics[topic]) {
            topics[topic] = [];
        }

        var token = (++subUid).toString();
        topics[topic].push({
            token: token,
            func: func
        });

        return token;
    };

    return {
        publish: publish,
        subscribe: subscribe,
        installTo: function(obj) {
            obj.publish = publish;
            obj.subscribe = subscribe;
        }
    }
}());


// 具体应用
var mod1 = {
    run: function(arg) {
        console.log('mod1 received ' + arg);
    }
};
var mod2 = {};
var topic = 'myTopic';
mediator.installTo(mod1);
mediator.installTo(mod2);
// mod1订阅消息
mod1.subscribe(topic, function(t, arg) {
    mod1.run(arg);
});
// mod2发布消息
mod2.publish(topic, 'data');

中介者模式和观察者模式的区别就是

  1. 中介模式强调同事(colleague)之间的交互,需要知道所有同事的信息,相互影响(买房的时候,中介需要知道双方需求和信息)
  2. 观察者模式中,其实是发布和订阅模式,发布者不需要知道订阅的信息(收听广播,广播站只管发布,不需要接收订阅者信息)

访问者模式

  1. 定义:通过继承封装一些该数据类型不具备的属性,
  2. 作用:主要将稳定的数据结构和易变的操作分开,方便扩展多变的操作方法而保持稳定的数据结构
  3. 代码
// 定义奖金的访问者,在js中简单的用一个函数模拟,
// 如果在c#等强类型语言中,需要声明一个接口,不同的visitor实现不同的计算奖金方法,
// 比如下面的管理者和开发者奖金不一样
function bonusVisitor(employee) {
  if (employee instanceof Manager)
    employee.bonus = employee.salary * 2;
  if (employee instanceof Developer)
    employee.bonus = employee.salary;
}

// 定义员工类,注意继承此类的必须都带有accept这个接受visitor的方法,
// 就是用来接待访问者,进而内部用访问者调用自己方法实现一些操作,
// 此例中直接调用函数visitor(this)
class Employee {

  constructor(salary) {
    this.bonus = 0;
    this.salary = salary;
  }

  accept(visitor) {
    visitor(this);
  }
}

// 管理者实现员工类
class Manager extends Employee {
  constructor(salary) {
    super(salary);
  }
}

// 开发者实现员工类
class Developer extends Employee {
  constructor(salary) {
    super(salary);
  }
}

使用

// 管理员工集合
    let employees = [];

    // 不同的员工
    const john = new Developer(4000);
    const christian = new Manager(10000);

   // 放入员工集合中
    employees.push(john);
    employees.push(christian);

     // 分别接收访问者并调用
    employees.forEach(e => {
      e.accept(bonusVisitor);
    });

员工数据是稳定的,但是加薪的操作是不确定的,访问模式将两者分开。

状态模式

  1. 定义:一个对象状态改变会导致行为变化
  2. 作用:解决复杂的if判断
  3. 代码
var State = function(){
    var States = {
        state0 : function(params){
            //your coding...
            console.log('state0~');
        },
        state1 : function(params){
            //your coding...
            console.log('state1~');
        },
        state2 : function(params){
            //your coding...
            console.log('state2~');
        },
        state3 : function(params){
            //your coding...
            console.log('state3~');
        },
    };
    function getResult(result){
        States['state'+result] && States['state'+result]();
    }
    return{
        setState : getResult
    } 
}();

State.setState(0);

注解:将if else转化为 map的过程 的过程

策略模式

  1. 定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间可以相互替换,独立于使用算法的客户
  2. 作用: 把算法从业务中单独抽离出来
  3. 代码
var tween = {
     linear: function(t, b, c, d){
         return c*t/d + b;
     },
     easeIn: function(t, b, c, d){
         return c * ( t /= d ) * t + b;
     },
     strongEaseIn: function(t, b, c, d){
         return c * ( t /= d ) * t * t * t * t + b;
     },
     strongEaseOut: function(t, b, c, d){
        return c * ( ( t = t / d -1 ) * t * t * t * t + 1 ) + b;
     },
     sineaseIn: function(t, b, c, d){
         return c * ( t /= d ) * t * t + b;
     },
     sineaseOut: function(t, b, c, d){
        return c * ( ( t = t / d -1 ) * t * t + 1 ) +b;
     }
 };


/*
 * 负责启动运动动画
 */
Animate.prototype.start = function( propertyName, endPos, duration, easing ){
    this.startTime = +new Date; // 启动动画的时间
    this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; // dom 节点的初始位置
    this.propertyName = propertyName; // dom 节点需要被改变的 CSS 属性名
    this.endPos = endPos; // dom 节点的目标位置
    this.duration = duration; // 动画的持续时间
    this.easing = tween[ easing ]; // 缓动算法

    // 启动动画定时器
    var self = this;
    var timeId = setInterval(function(){
        if( self.step() === false){
            clearInterval(timeId);
        }
    },20);
};

桥接模式

  1. 定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化
  2. 代码
   //bad
    addEvent(Element,'click',getBeerById)
    function getBeerById(e){
      // 事件对象被作为参数传递给函数,而本例并没有使用这个参数,只是从this对象中获取id
      var id = this.id;
      asyncRequest('GET',`beer.uri?id=${id}`,function (res) {
         console.log(`Request Beer:${res.responseText}`)  
      }) 
    }
    // good
    function getBeerById(id,callback) {
      asyncRequest('GET',`beer.uri?id=${id}`,function (res) {
        // 回调 传入返回值
        callback(res.responseText)
      }) 
    }
    addEvent(Element,'click',getBeerByIdBridge)
    function getBeerByIdBridge(e){
      // 把id作为参数传递给getBeerById函数是合情合理的,这里使用一个回调函数把回应结果返回 现在我们将针对接口而不是实现进行编程 
      getBeerById(this.id,function (beer) {
        console.log(`Request Beer:${res.responseText}`)
      })
    }

我们用了两种方式来实现,第一种方式把事件对象与getBeerById捆绑在一起,那么它只在本次事件中适用。第二种方式使用桥接模式把抽象隔离开来后,getBeerById 函数不再和事件对象捆绑在一起,也就扩大了它的适用范围

参照链接

  1. [https://www.jianshu.com/p/f362d67ed659]
  2. [https://www.jianshu.com/p/7e582eec6b84]
  3. [https://www.cnblogs.com/Medeor/p/5001841.html]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容