行为型设计模式

模版方法模式

核心
  • 定义:父类定义一组算法骨架,子类在复用父类的同时,扩展功能
  • 应用:公共组件的封装,基础类组件封装(定制化组件,提供多个类型组件的统一修改入口)
  • 重点:子类型,共享父类型组件的样式及功能,利于功能统一,代码维护和扩展。(方法重用、共享)
示例一:提示框
  • 需求:封装一个基本提示框,在其基础上扩展不同类型的子类提示框(利于样式统一的维护)
  • js
// 模版类 基础提示框(面板,提示内容,关闭按钮,确认按钮)
var g = (tag) => document.createElement(tag);
var Alert = function (data) {
  if (!data) return;
  // 默认数据
  data = {
    confirm: "确认",
    success() {},
    fail() {},
    ...data,
  };
  this.content = data.content;
  // 标签
  this.panel = g("div");
  this.contentNode = g("p");
  this.contentNode.innerHTML = this.content;
  this.confirmBtn = g("span");
  this.confirmBtn.innerHTML = data.confirm;
  this.closeBtn = g("b");
  // 样式
  this.panel.className = "alert";
  this.confirmBtn.className = "a-confirm";
  this.closeBtn.className = "a-close";
  // 方法
  this.success = data.success;
  this.fail = data.fail;
};

// 原型方法
Alert.prototype = {
  init() {
    // 生成提示框
    [this.closeBtn, this.contentNode, this.confirmBtn].forEach((el) => {
      this.panel.appendChild(el);
    });
    document.body.appendChild(this.panel);
    this.bindEvent();
    this.show();
  },
  // 绑定事件
  bindEvent() {
    this.closeBtn.onclick = () => {
      this.fail();
      this.hide();
    };
    this.confirmBtn.onclick = () => {
      this.success();
      this.hide();
    };
  },
  hide() {
    this.panel.style.display = "none";
  },
  show() {
    this.panel.style.display = "block";
  },
};

// 提示框(右侧按钮)
var RightAlert = function (data) {
  Alert.call(this, data);
  this.confirmBtn.className += " right";
};
RightAlert.prototype = Object.create(Alert.prototype);
RightAlert.prototype.constructor = RightAlert;
console.log(RightAlert.prototype);

// 标题提示框
var TitleAlert = function (data) {
  Alert.call(this, data);
  this.title = data.title;
  this.titleNode = g("h3");
  this.titleNode.innerHTML = this.title;
};
TitleAlert.prototype = new Alert();
TitleAlert.prototype.init = function () {
  this.panel.insertBefore(this.titleNode, this.panel.firstChild);
  Alert.prototype.init.call(this);
};
console.log(TitleAlert.prototype);

// 标题提示框+取消按钮
var CancelAlert = function (data) {
  TitleAlert.call(this, data);
  this.cancel = data.cancel;
  this.cancelBtn = g("span");
  this.cancelBtn.className = "cancel";
  this.cancelBtn.innerHTML = this.cancel || "取消";
};

// 继承 Alert 原型
CancelAlert.prototype = new Alert();
CancelAlert.prototype.init = function () {
  // 继承 TitleAlert原型 的 init
  TitleAlert.prototype.init.call(this);
  this.panel.appendChild(this.cancelBtn);
};
CancelAlert.prototype.bindEvent = function () {
  // 继承事件
  TitleAlert.prototype.bindEvent.call(this);
  this.cancelBtn.onclick = () => {
    this.fail();
    this.hide();
  };
};
  • 调用
new Alert({
  content: "我是基础弹窗",
}).init();
new RightAlert({
  content: "我是右侧按钮弹窗",
}).init();
new TitleAlert({
  content: "我是标题提示框",
  title: "我是标题",
}).init();
new CancelAlert({
  content: "标题提示框+取消按钮",
  title: "我是标题",
  cancel: "取消按钮",
  fail() {
    console.log("fail..");
  },
}).init();
  • 效果图
    在这里插入图片描述
示例二:多类导航
  • 需求:创建一个基础导航,在其基础上,扩展出消息导航和链接导航
  • js
// 格式化字符串
function formatString(str, data) {
  // 全局匹配 {#xxx#} -> xxx(数字,字母,下划线)
  return str.replace(/\{#(\w+)#\}/g, function (match, key) {
    console.log(match);
    return (data && data[key]) || "";
  });
}

// 基础导航
var Nav = function (data) {
  // 模板
  this.item = '<a href="{#href#}" title="{#title#}">{#name#}</a>';
  this.html = "";
  for (const v of data) {
    this.html += formatString(this.item, v);
  }
  return this.html;
};

// 信息导航
var NumNav = function (data) {
  // 模板
  var tpl = "<b>{#num#}</b>";
  for (let i = 0; i < data.length; i++) {
    data[i].name += formatString(tpl, data[i]);
  }
  return Nav.call(this, data);
};

// 网址导航
var LinkNav = function (data) {
  var tpl = "<span>{#link#}</span>";
  for (let i = 0; i < data.length; i++) {
    data[i].name += formatString(tpl, data[i]);
  }
  return Nav.call(this, data);
};
  • 调用
<script>
const list = [
  {
    href: "http://www.baidu.com",
    title: "百度一下",
    name: "百度",
  },
  {
    href: "http://www.baidu.com",
    title: "百度一下1",
    name: "百度1",
  },
  {
    href: "http://www.baidu.com",
    title: "百度一下2",
    name: "百度2",
  },
];
document.getElementById("main").innerHTML = Nav(list);
document.getElementById("main1").innerHTML = NumNav(
  list.map((item) => ({
    ...item,
    num: Math.floor(Math.random() * 5) + 5,
  }))
);
document.getElementById("main2").innerHTML = LinkNav(
  list.map((item, index) => ({
    ...item,
    link: `(网址:${new Array(index + 3).fill("x").join("")})`,
  }))
);
</script>
  • 效果图
    在这里插入图片描述

观察者模式

核心
  • 定义:又叫发布 - 订阅者模式,或消息机制。定义了一种 依赖关系,解耦对象间的关系(主体对象和观察者)
  • 应用:前端中的事件机制(注册和解绑由开发者实现,由浏览器等发布事件),功能模块间的通信(解耦)
  • 核心:解决代码的耦合问题,实现模块间的通信
观察者对象实现
  • 观察者对象实现(容器对象):负责注册、注销、发布功能的实现
// 观察者对象(绑定,解绑,发布)
var Observer = (function () {
  // 消息容器
  var _message = {};
  return {
    // 绑定,type - 消息类型,fn - 回调函数
    on(type, fn) {
      if (!_message[type]) _message[type] = [];
      _message[type].push(fn);
      return this;
    },
    // 解绑
    off(type, fn) {
      if (!_message[type] instanceof Array) return;
      var len = _message[type].length,
        i = len - 1;
      // 从后往前遍历,优化删除元素的性能
      for (; i >= 0; i--) {
        if (_message[type][i] === fn) {
          _message[type].splice(i, 1);
        }
      }
      return this;
    },
    // 发布
    emit(type, args = {}) {
      if (!_message[type]) return;
      // 消息信息
      var events = {
        type,
        args,
      };
      var i = 0,
        len = _message[type].length;
      for (; i < len; i++) {
        _message[type][i].call(this, events); // this 绑定off的调用者
      }
      return this;
    },
  };
})();
if (module) {
  module.exports = Observer;
}
  • 基本功能测试
const Observer = require("./common");
// 绑定和删除函数时,地址相同
const f1 = (events) => console.log("触发f1,参数:", events);
const f2 = (events) => console.log("触发f2,参数:", events);
const f3 = function () {
  console.log("this === Observer:", this === Observer);
};
Observer.on("click", f1);
Observer.on("click", f2);
Observer.on("click", f3);
Observer.emit("click", { a: 1, b: 2 });
Observer.off("click", f1);
Observer.off("click", f3);
Observer.emit("click", { a: 3, b: 2 });
  • 效果图
    在这里插入图片描述
示例一:新闻评论功能
  • 需求:功能分三块:用户信息(展示消息数),消息列表(展示和删除消息),提交消息(新增消息)

  • 说明

    • 用户信息模块,信息接收者
    • 消息列表,接受者和发送者
    • 提交消息,发送者
  • js

// 新闻评论功能 模块通信(用户消息信息+消息列表+提交表单)
// 根据ID获取dom
function $(id) {
  return document.getElementById(id);
}
// 创建dom
function g(tag) {
  return document.createElement(tag);
}

// 用户信息模块(显示消息)
(function () {
  let num = 0;
  function change(e) {
    num += e.args.num;
    $("msg_num").innerHTML = num;
  }
  // 注册 添加/删除事件
  Observer.on("add", change).on("remove", change);
})();

// 消息列表模块(显示消息+添加消息)
(function () {
  function add(e) {
    var text = e.args.text,
      ul = $("msg"),
      li = g("li"),
      span = g("span");
    li.innerHTML = text;
    span.innerHTML = "删除";
    span.onclick = () => {
      ul.removeChild(li);
      // 发布删除事件
      Observer.emit("remove", { num: -1 });
    };
    li.appendChild(span);
    ul.appendChild(li);
  }
  // 注册添加事件
  Observer.on("add", add);
})();

// 提交表单模块
(function () {
  $("submit").onclick = function () {
    var text = $("input");
    if (!text.value) return;
    // 发布添加事件
    Observer.emit("add", {
      text: text.value,
      num: 1,
    });
    text.value = "";
  };
})();
  • 调用
<script src="./common.js"></script>
<body>
<div>
  <p>用户信息</p>
  <span>消息数:</span>
  <span id="msg_num"></span>
</div>
<div>
  消息列表
  <ul id="msg"></ul>
</div>
<div>
  <p>提交消息</p>
  <textarea id="input"></textarea>
  <button id="submit">提交</button>
</div>
<script src="./index2.js"></script>
</body>
  • 效果图
    在这里插入图片描述
示例二:对象间解耦
  • 需求:课堂老师提问+学生回答
  • 实现
// 对象间解耦

// 示例:课堂老师提问+学生回答

const Observer = require("./common");
// 学生类
var Student = function (res) {
  // 回答结果
  this.res = res;
  // 回答问题动作
  this.say = () => {
    console.log(this.res);
  };
};

// 回答问题方法
Student.prototype.answer = function (question) {
  // 注册问题事件
  Observer.on(question, this.say);
};

// 学生睡觉,不能回答问题
Student.prototype.sleep = function (question) {
  console.log(this.res + " " + question + " 已注销");
  // 解绑问题事件
  Observer.off(question, this.say);
};

// 教师类
var Teacher = function () {};
Teacher.prototype.ask = function (question) {
  console.log("老师问问题:", question);
  // 触发问题事件
  Observer.emit(question);
};

var s1 = new Student("学生1回答");
var s2 = new Student("学生2回答");
var s3 = new Student("学生3回答");

// 学生注册事件
s1.answer("老师的问题1");
s1.answer("老师的问题2");
s2.answer("老师的问题1");
s3.answer("老师的问题1");
s3.answer("老师的问题2");

// 学生睡觉(在问题1)
s3.sleep("老师的问题1");

var t = new Teacher();
// 老师问问题
t.ask("老师的问题1");
t.ask("老师的问题2");

在这里插入图片描述

状态模式

核心
  • 定义:对象的行为,由其内部状态决定(简化分支逻辑,利于扩展)
  • 核心:分支逻辑较多时,利用 js对象 可以根据 key 直接查找到 value 的特性,用对象的方法替代分支逻辑
  • 应用:动态组件,类型常量定义等
示例一:投票活动
  • 需求:给投票的结果,设置不同的处理函数
  • 核心:利用闭包,生成内部变量,控制访问
// 投票结果状态对象
var ResultState = (function () {
  var States = {
    state0() {
      console.log("第一种情况");
    },
    state1() {
      console.log("第二种情况");
    },
    state2() {
      console.log("第三种情况");
    },
    state3() {
      console.log("第四种情况");
    },
  };
  function show(res) {
    States[`state${res}`] && States[`state${res}`]();
  }
  // 闭包
  return {
    show,
  };
})();

ResultState.show(1);
示例二:超级玛丽
  • 需求:超级玛丽游戏,人物有多种状态:跳跃、移动、射击、蹲下…,状态可以搭配组合。对其动作行为进行封装。
  • 核心:内部变量控制人物的当前状态,提供访问和修改状态的接口
// 超级玛丽状态类
var MarryState = function () {
  // 状态(私有变量)
  var _state = {};
  // 动作类型
  var types = {
    jump() {
      console.log("jump"); // 跳跃
    },
    move() {
      console.log("move"); // 移动
    },
    shoot() {
      console.log("shoot"); // 射击
    },
    quat() {
      console.log("quat"); // 蹲下
    },
  };
  // 动作类
  var Action = {
    changeState() {
      var arg = arguments;
      _state = {};
      for (var i = 0; i < arg.length; i++) {
        _state[arg[i]] = true; // 添加动作
      }
      return this;
    },
    goes() {
      console.log("触发一次动作");
      for (var key in _state) {
        types[key] && types[key]();
      }
      return this;
    },
  };
  return {
    change: Action.changeState,
    goes: Action.goes,
  };
};

MarryState()
  .change("quat", "shoot", "move") // 蹲下-射击-移动
  .goes()
  .goes()
  .change("move", "jump") // 移动-跳跃
  .goes();
console.log("--------------------------------");
// 实例化后使用(不影响原对象)
var marry = new MarryState();
marry
  .change("quat", "shoot", "move") // 蹲下-射击-移动
  .goes()
  .goes()
  .change("move", "jump") // 移动-跳跃
  .goes();
  • 效果图
    在这里插入图片描述

策略模式

核心
  • 定义:封装一组算法,用于特定的功能
  • 应用:表单的验证方法,业务功能相似的工具方法封装
  • 重点:使业务和功能解耦,利于封装和维护
示例一:商品促销
  • 需求:商品促销,满减活动,打折活动
// 价格策略对象
var PriceStrategy = (function () {
  // 内部算法对象
  var strategy = {
    // 100 返 30
    return30(price) {
      return +price + parseInt(price / 100) * 30;
    },
    // 100 返 50
    return50(price) {
      return +price + parseInt(price / 100) * 50;
    },
    // 9折
    percent90(price) {
      return price * 0.9;
    },
    // 8折
    percent80(price) {
      return price * 0.8;
    },
  };
  // 策略算法接口
  return function (type, price) {
    return strategy[type] && strategy[type](price);
  };
})();

console.log(PriceStrategy("percent90", "400.9"));
示例二:表单验证
  • 需求:验证不同的表单元素,是否符合要求(必填、手机号、邮箱、网址…)
// 表单验证
var InputStrategy = (function () {
  var strategy = {
    notNull(value) {
      return /\s+/.test(value) ? "请输入内容" : "";
    },
    number(value) {
      return /^[0-9]+(\.[0-9]+)?$/.test(value) ? "" : "请输入正确的数字";
    },
    phone(value) {
      return /^\d{3}-\d{8}$|^\d{4}-\d{7}$/.test(value)
        ? ""
        : "请输入正确的电话号码";
    },
  };
  return {
    check(type, value) {
      value = value.replace(/^\s+|\s+$/g, "");
      return strategy[type] ? strategy[type](value) : "没该类型方法";
    },
    addStrategy(type, fn) {
      strategy[type] = fn;
    },
  };
})();

// 返回验证信息,空为通过
console.log(InputStrategy.check("phone", " 333-88888888  ")); // 输出 空

职责链模式

核心
  • 定义:解决请求的发送者和请求的接受者之间的耦合
  • 简化拆分 发送 - 接收 的流程(类似于中间件)
  • 应用:业务需求的渐进式开发(向后兼容)。类似于渐进式开发,业务由易 -> 难的递增性开发(react全家桶,中间件…)(利于单元测试)
示例
  • 需求:表单元素的异步校验(请求后端校验)
  • 功能拆分
    • 请求模块
    • 数据处理模块
    • 创建组件模块
    • 测试模块
  • 服务端:使用 express 做 web 服务器
    • 监听 /getData的 get 请求,服务端页面渲染
    • 校验不同的表单元素(组合校验)
var express = require("express");

var app = express();
app.use(express.static("./"));
app.get("/getData", function (req, res) {
  const { key, value } = req.query;
  if (key === "name") {
    if (value.length < 2) {
      res.send(["请输入至少2个"]);
      return;
    }
  }
  if (key === "num") {
    var errs = [];
    if (!/^\d+$/.test(value)) {
      errs.push("请输入数字");
    }
    if (value.length < 4) {
      errs.push("请输入至少四位");
    }
    if (errs.length) return res.send(errs);
  }
  res.send([]);
});

app.listen(3000);
  • js
// ajax简单封装(未兼容IE)
var sendData = function (data, dealType, dom) {
  var xhr = new XMLHttpRequest();
  xhr.responseType = "json";
  var url = "getData?";
  xhr.onload = function (e) {
    console.log(xhr);
    const { status } = xhr;
    if ((status >= 200 && status < 300) || status === "304") {
      dealData(xhr.response, dealType, dom);
    } else {
      // 失败
    }
  };
  for (var key in data) {
    url += `&${key}=${data[key]}`;
  }
  xhr.open("get", url, true);
  xhr.send(null);
};

var dealData = function (data, dealType, dom) {
  var dataType = Object.prototype.toString.call(data);
  switch (dealType) {
    case "sug":
      // 数组
      if (dataType === "[object Array]") {
        return createSug(data, dom);
      }
      // 对象转数组
      if (dataType === "[object Object]") {
        var list = [];
        for (var key in data) {
          list.push(data[key]);
        }
        return createSug(list, dom);
      }
      // 其他数据转数组
      return createSug([data], dom);
    case "validate":
      return createValidate(data, dom);
  }
};

// 提示组件
var createSug = function (data, dom) {
  dom.parentNode.getElementsByTagName("ul")[0].innerHTML = data.reduce(
    (prev, next) => {
      return prev + `<li>${next}</li>`;
    },
    ""
  );
};

// 校验组件(模拟)
var createValidate = function (data, dom) {
  dom.parentNode.getElementsByTagName("span")[0].innerHTML = data;
};

// 测试
var input = document.getElementsByTagName("input");
[...input].forEach((el) => {
  const { dealtype: dealType, trigger, key } = el.dataset;
  // 绑定事件
  el[`on${trigger}`] = function (e) {
    setTimeout(() => {
      sendData({ value: el.value, key }, dealType, this);
    }, 0);
  };
});
  • 调用
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div>
      <div>
        <label>名称(验证功能输入框)</label>
        <input
          placeholder="名称(验证功能输入框)"
          data-dealType="validate"
          data-trigger="change"
          data-key="name"
        />
        <ul></ul>
        <span></span>
      </div>
      <div>
        <label>数字(提示功能输入框)</label>
        <input
          placeholder="数字(提示功能输入框)"
          data-dealType="sug"
          data-trigger="input"
          data-key="num"
        />
        <ul></ul>
        <span></span>
      </div>
    </div>
    <script src="./index.js"></script>
  </body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值