结构型设计模式

  • 结构型设计模式,关注于如何将类或对象,组合成更大、更复杂的结构

外观模式

核心
  • 目的:为复杂的子系统的访问,提供统一的、简便的接口
  • 应用:API的兼容性封装,代码库的功能封装(简化操作)
  • 重点:二次封装,接口具体实现和上层代码调用解耦
实现
  • 页面点击事件(考虑兼容性)
// 页面点击事件封装(外观模式)
function addEvent(dom, type, fn) {
  // DOM2级事件(支持document.addEventListener)
  if (dom.addEventListener) {
    dom.addEventListener(type, fn, false);
  }
  // 支持document.attachEvent
  else if (dom.attachEvent) {
    dom.attachEvent("on" + type, fn);
  }
  // DOM0级事件
  else {
    dom["on" + type] = fn;
  }
}

// 获取事件对象
function getEvent(e) {
  return e || window.event;
}

// 获取元素
function getTarget(e) {
  var event = getEvent(e);
  return event.target || event.srcElement;
}

// 阻止默认行为
function preventDefault(e) {
  var event = getEvent(e);
  if (event.preventDefault) {
    event.preventDefault();
  } else {
    event.returnValue = false;
  }
}

// 禁用页面所有a标签的跳转
[...document.getElementsByTagName("a")].forEach((item) => {
  addEvent(item, "click", function (e) {
    preventDefault(e);
    console.log(getTarget(e));
  });
});
  • dom 方法库(简化写法)
var A = {
  g(id) {
    return document.getElementById(id);
  },
  css(id, key, value) {
    document.getElementById(id).style[key] = value;
  },
  //...
};

适配器模式

核心
  • 定义:将一个类的接口,转化为另外一个接口(兼容不同结构的数据或方法)
  • 重点:重新包装对象,了解适配对象的内部结构(与外观模式的区别)。对象解耦
适配同类框架
  • 框架迁移兼容
    • A 框架 迁移到 jQuery
    • 让 A 的调用代码,适配 jQuery
  • A 框架使用(方法的调用类似 jQuery)
A(function(){
	A('button').on('click' , function(){
		//...
	})
})

// 改变 A变量 的指向
window.A = A = jQuery
适配异类框架
  • A 框架使用
var A = A || {};
A.g = function (id) {
  return document.getElementById(id);
};
A.on = function (id, type, fn) {
  // 获取元素对象
  var dom = typeof id === "string" ? this.g(id) : id;
  if (dom.addEventListener) {
    dom.addEventListener(type, fn, false);
  } else if (dom.attachEvent) {
    dom.attachEvent("on" + type, fn);
  } else {
    dom["on" + type] = fn;
  }
};

// 使用(window窗口加载后,按钮绑定点击事件)
A.on(window, 'load', function(){
  A.on('btn', 'click', function(){
    //...
  })
})
  • 适配
// jQuery 适配 A 框架
var A = A || {};
A.g = function (id) {
  return $(id).get(0);
};
A.on = function (id, type, fn) {
  // 返回 jquery 对象
  var dom = typeof id === "string" ? $(`#${id}`) : $(id);
  dom.on(type, fn);
};

// 使用(window窗口加载后,按钮绑定点击事件)
A.on(window, "load", function () {
  A.on("btn", "click", function () {
    //...
  });
});
参数适配器
  • 函数参数,赋默认值(不修改原对象)
function f(obj = {}) {
  var _adapter = {
    name: "nx",
    title: "tx",
    age: 20,
    color: "red",
    prize: 40,
    //...
    // 或者 ...obj
  };
  for (var i in _adapter) {
    _adapter[i] = obj[i] || _adapter[i];
  }
  // do some
}
服务器端数据适配
  • 解决了 前后端数据依赖
    • 前端定义数据格式
    • 前端定义字段名称
    • 数据处理
  • 示例
// 处理后端返回的数据obj,返回所需格式的数据
function formatData(obj) {
  return {
    a: format.A(obj.a),
    arr: [obj.c, format.B(obj.d)],
    keys: [obj.key1, obj.key2, obj.key3],
    //...
  };
}

// 数据格式化工具(模拟)
var format = {
  A() {},
  B() {},
};

代理模式

核心
  • 定义:由于一个对象,不能直接访问另外一个对象。通过添加中介的方式实现访问(类似中间件)
  • 主要用于解决跨域问题(xhr)
应用一:站长统计
  • 统计用户的信息
  • 实现:img,video, script等标签的 src 属性,可跨域发送请求(get 方式)
// 统计代理
var Count = (function () {
  var _img = new Image();
  return function (params) {
    // 请求地址
    var str = "http://localhost:3000/stat/a.png?";
    for (let key in params) {
      if (str) str += "&";
      str += key + "=" + params[key];
    }
    _img.src = str;
  };
})();
Count({ a: 1, b: 2, c: 3 });


// server.js
var express = require("express");
var app = express();

app.use("/stat", (req, res) => {
  console.log(req.query);
  res.status(204).end();
});
app.listen(3000);

在这里插入图片描述

应用二:JSONP
  • 通过 jsonp,实现跨域 get 请求(script标签实现)
  • 服务端返回调用函数的字符串
<!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>
    <script>
      function jsonpCallback(status, data) {
        console.log(status, data);
      }
      var script = document.createElement("script");
      const data = {
        a: 1,
        str: "abdc",
        arr: [
          {
            key: 111,
          },
        ],
      };
      script.src = `http://localhost:3000/jsonp?fn=jsonpCallback&data=${JSON.stringify(
        data
      )}`;
      // 需要放到页面,才能请求
      document.body.appendChild(script);
    </script>
  </body>
</html>

// server.js
var express = require("express");
var app = express();

app.use("/jsonp", (req, res) => {
  console.log(req.query);
  const { fn, data } = req.query;
  res.status(200).end(
    `
      ${fn}('success', ${data})
    `
  );
});
app.listen(3000);
  • 本地打开网页(控制台输出)
    在这里插入图片描述
应用三:代理模版
  • 原理:相同域中,页面之间的互相调用
  • 实现
    • X 域 中有 A , B 页面, 服务器在 Y 域
    • A 表单请求到 Y时,重定向到 B,B 加载后调用 A 中的方法
      • form 表单的返回结果 B,嵌套在 iframe 中
      • B 中通过 iframe, 找到 A 中的方法名,用 eval 调用
      • Y 中可以做后端数据处理
  • 目录结构
    • public
      • A.html
      • B.html
    • server.js(Y 域,处理请求)
    • server2.js(X域, 访问 public 页面)
  • A.html
<!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>
    <style>
      input {
        width: 200px;
      }
    </style>
  </head>
  <body>
    <script>
      function callback(data) {
        console.log("成功数据:", data);
      }
    </script>
    <iframe name="proxyIframe" id="proxyIframe" src=""></iframe>
    <form
      action="http://localhost:3000/template"
      method="post"
      target="proxyIframe"
    >
      <input name="fn" value="callback" />
      <input name="proxy" value="http://localhost:3001/B.html" />
      <input type="submit" value="提交" />
    </form>
  </body>
</html>
  • B.html
<!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>i am b</div>
    <script>
      window.onload = function () {
        if (top === self) return;
        var arr = location.search.substr(1).split("&");
        var callback, args;
        for (let v of arr) {
          const item = v.split("=");
          if (item[0] === "callback") {
            callback = item[1];
          } else if (item[0] === "arg") {
            args = item[1];
          }
        }
        try {
          eval(`top.${callback}("${args}")`);
        } catch (e) {
          console.log(e);
        }
      };
    </script>
  </body>
</html>
  • server.js
var express = require("express");
var app = express();

app.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

app.post("/template", (req, res) => {
  // console.log(req.body);
  const { fn, proxy } = req.body;
  res.redirect(`${proxy}?callback=${fn}&arg=success11`);
});
app.listen(3000);
  • server2.js
var express = require("express");
var app = express();

app.use(express.static("public"));
app.listen(3001);

在这里插入图片描述

装饰者模式

核心
  • 定义:在不改变原有对象的基础上,对其进行包装拓展,满足新需求
  • 目的:在原有的功能上,新增功能(类似中间件)
实现
var decorator = function (id, fn) {
  var el = document.getElementById(id);
  if (typeof el.onclick === "function") {
    var oldClickFn = el.onclick;
    el.onclick = function () {
      oldClickFn();
      fn();
    };
  } else {
    el.onclick = fn;
  }
};

var el = document.createElement("div");
el.id = "id1";
el.style.padding = "40px";
el.innerText = "点我";
document.body.appendChild(el);
// 为同一元素,绑定多个事件
decorator("id1", () => {
  console.log("do some 111");
});
decorator("id1", () => {
  console.log("do some 222");
});
decorator("id1", () => {
  console.log("do some 333");
});

桥接模式

核心
  • 定义:在系统沿着多个纬度变化时,不增加其复杂度,并解耦(相同逻辑提取)
  • 目的:实现层(如元素绑定事件)和抽象层(业务逻辑)分离
  • 开闭原则:对拓展开放,对修改关闭(功能点拆分,功能拓展)
应用一:为不同的元素,添加相同的事件交互
// 添加事件交户(简单模拟)

// 抽象 设置颜色的功能
function changeColor(dom, color, bg) {
  dom.style.color = color;
  dom.style.backgroundColor = bg;
}

// 为元素绑定事件
[...document.getElementsByTagName("div")].forEach((el) => {
  el.onmouseover = function () {
    changeColor(this, "red", "blue");
  };
  el.onmouseout = function () {
    changeColor(this, "blue", "red");
  };
});
应用二:多元化对象
  • 拆分或抽象复杂的对象,变成一个个独立的单元。通过元的组合(桥接),创建复杂的对象。元之间解耦。
// canvas 实现跑步游戏,对象:人,精灵,球...

// 多维变量类
// 运动单元
function Speed(x, y) {
  this.x = x;
  this.y = y;
}
Speed.prototype.run = function () {
  console.log("运动起来");
};

// 着色单元
function Color(cl) {
  this.color = cl;
}
Color.prototype.draw = function () {
  console.log("绘制彩色");
};

// 变形单元
function Shape(sp) {
  this.shape = sp;
}
Shape.prototype.change = function () {
  console.log("改变形状");
};

// 说话单元
function Speek(wd) {
  this.word = wd;
}
Speek.prototype.say = function () {
  console.log("我说话了");
};

// 创建球类
function Ball(x, y, c) {
  this.speed = new Speed(x, y);
  this.color = new Color(c);
}
Ball.prototype.init = function () {
  this.speed.run();
  this.color.draw();
};

new Ball(10, 10, "red").init();
// 人物类
// 精灵类
// ...

组合模式

核心
  • 定义:又称 部分-整体模式。将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性
  • 目的:通过对部分的组合,简化和丰富复杂的整体(类似于创建虚拟DOM)
应用一:新闻模块
  • 容器类的原型方法,做了二次封装
  • 对象类按参数统一处理(也可拆分成单独模块,或按配置划分不同的类别)
  • 根据不同的元素分组和class,显示UI
/**
 * 新闻模块(树形结构)
 * - 容器类
 *    - 成员集合类(容器类)
 *    - 新闻组合体类(容器类)
 *    - 等等(由以下新闻对象类-非容器类,任意搭配组成)
 *        - 图片新闻类
 *        - 图标新闻类
 *        - 简单新闻类
 *        - type新闻类
 *        - 等等
 */
var abstractFn = function () {
  throw new Error("请重写抽象方法");
};

// 寄生式继承
function inheritPrototype(subClass, supClass) {
  var p = Object.create(supClass.prototype);
  p.constructor = subClass;
  subClass.prototype = p;
}

// 统一的接口,新闻虚拟父类(容器类和对象类都继承它)
var News = function () {
  this.children = [];
  this.element = null;
};
// 原型抽象方法
News.prototype = {
  init: abstractFn,
  add: abstractFn,
  getElement: abstractFn,
};

var utils = {
  init(tag, opt = {}) {
    this.element = document.createElement(tag);
    for (let key in opt) {
      this.element[key] = opt[key];
    }
  },
  add(child) {
    this.children.push(child);
    this.element.appendChild(child.getElement());
    return this;
  },
};

// 容器类
var Container = function (id, parent) {
  // 构造函数继承
  News.call(this);
  this.id = id;
  this.parent = parent;
  this.init();
};

// 原型的寄生式继承
inheritPrototype(Container, News);

// 原型属性(不能改变prototype.constructor)
Container.prototype.init = function () {
  utils.init.call(this, "ul", { id: this.id, className: "new-container" });
};
Container.prototype.add = function (child) {
  return utils.add.call(this, child);
};
Container.prototype.getElement = function () {
  return this.element;
};
Container.prototype.show = function () {
  this.parent.appendChild(this.element);
};

// --------------------------------------
// Item容器类(成员集合)
var Item = function (className = "") {
  // 构造函数继承
  News.call(this);
  this.className = className;
  this.init();
};

// 原型的寄生式继承
inheritPrototype(Item, News);

Item.prototype.init = function () {
  utils.init.call(this, "li", { className: this.className });
};
Item.prototype.add = function (child) {
  return utils.add.call(this, child);
};
Item.prototype.getElement = function () {
  return this.element;
};

// NewsGroup容器类(新闻组合体)
var NewsGroup = function (className = "") {
  // 构造函数继承
  News.call(this);
  this.className = className;
  this.init();
};

// 原型的寄生式继承
inheritPrototype(NewsGroup, News);

NewsGroup.prototype.init = function () {
  utils.init.call(this, "div", { className: this.className });
};
NewsGroup.prototype.add = function (child) {
  return utils.add.call(this, child);
};
NewsGroup.prototype.getElement = function () {
  return this.element;
};

// -------------------------------------------
// 新闻对象类(统一封装)
function NewObj(opt) {
  News.call(this);
  const _adapter = {
    url: "",
    href: "#",
    className: "normal",
    label: "",
    ...opt,
  };
  for (let key in _adapter) {
    this[key] = _adapter[key];
  }
  this.init();
}

inheritPrototype(NewObj, News);
NewObj.prototype.init = function () {
  let { type, className, href, label, text } = this;
  this.element = document.createElement("a");
  let labelClass = "";
  if (type === "img") {
    var img = new Image();
    img.src = this.url;
    this.element.appendChild(img);
  } else {
    if (label) {
      labelClass = `ui-${label}`;
      if (this.pos === "left") {
        text = `[${label}]${text}`;
      } else {
        text = `${text}[${label}]`;
      }
    }
    this.element.innerHTML = text;
  }

  this.element.className = `ui-${type} ${labelClass} ${className || ""}`;
  this.element.href = href;
};
// 无该方法,覆盖抽象方法
NewObj.prototype.add = function () {};
NewObj.prototype.getElement = function () {
  return this.element;
};

// 调用
new Container("news", document.body)
  .add(
    new Item("normal").add(
      new NewObj({
        text: "梅西不拿。。。",
      })
    )
  )
  .add(
    new Item("normal").add(
      new NewObj({
        text: "梅西不拿111。。。",
        label: "live",
      })
    )
  )
  .add(
    new Item("normal").add(
      new NewsGroup("has-img")
        .add(
          new NewObj({
            type: "img",
            url: "xxx",
            className: "small",
          })
        )
        .add(
          new NewObj({
            text: "313232火炮。。。",
          })
        )
    )
  )
  .add(
    new Item("normal").add(
      new NewObj({
        text: "火炮。。。",
        label: "CBA",
        pos: "left",
      })
    )
  )
  .show();

在这里插入图片描述

应用二:表单
  • 表单按照不同的元素类型和UI结构,划分成不同的UI组件(互相组合)
  • 结构配置:类似虚拟dom的JSX(结构化的标签)(描述性配置编程)
new Form("formId", document.body)
  .add(
    new Group("account", "账号").add(
      new FormItem({
        label: "用户名",
        key: "user_name",
        rightText: "4到6位数字",
      }),
      new FormItem({
        label: "密码",
        key: "pwd",
        rightText: "6到12位数字或字母",
      })
    )
  )
  .add(
    new Group("info", "信息").add(
      new FormItem({
        label: "昵称",
        key: "nic",
      }),
      new FormItem({
        label: "状态",
        key: "status",
      })
    )
  );

// 配置描述
var config = {
  id: "formId",
  container: document.body,
  list: [
    {
      label: "账号",
      key: "account",
      list: [
        {
          label: "用户名",
          key: "user_name",
          rightText: "4到6位数字",
          placeholder: "请输入用户名",
        },
        //...
      ],
    },
    //...
  ],
};

// 直接调用函数生成对应的元素
handle(config)

// handle实现

享元模式

核心
  • 定义:减少创建对象的数量,以减少内存占用和提高性能
  • 原理:提取共享的数据和方法,优化性能与代码
应用一:分页功能
  • 原理:页面列表始终展示一页的元素,分页时替换元素
  • js
// 分页器
// 获取页面的列表元素(最多显示5个)
var Flyweight = (function () {
  var created = [];
  function create() {
    var dom = document.createElement("div");
    document.getElementById("container").appendChild(dom);
    created.push(dom);
    return dom;
  }
  return {
    getDiv() {
      if (created.length < num) {
        return create();
      } else {
        const div = created.shift();
        created.push(div);
        return div;
      }
    },
  };
})();

var article = new Array(32)
  .fill(0)
  .map((item, index) => `我是第${index}个元素`);

var page = 0;
var num = 10;
var len = article.length;
for (var i = 0; i < num; i++) {
  if (article[i]) {
    Flyweight.getDiv().innerHTML = article[i];
  }
}

document.getElementById("next").onclick = function () {
  if (len < num) return;
  var n = (++page * num) % len; // 当前页的第一个新闻序号(新闻是首尾循环重复的)
  for (let j = 0; j < num; j++) {
    Flyweight.getDiv().innerHTML = article[n + j] || article[n + j - len];
  }
};
  • html
<!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 id="container"></div>
    <button id="next">下一页</button>
    <script src="./index.js"></script>
  </body>
</html>

在这里插入图片描述

应用二:享元动作
  • 创建角色时,都会有运动这个动作,封装共享(原型继承)
// 享元类
var Flyweight = {
  moveX(x) {
    this.x = x;
    return this;
  },
  moveY(y) {
    this.y = y;
    return this;
  },
};

// 人类
var Player = function (x, y, c) {
  this.x = x;
  this.y = y;
  this.color = c;
};
// 地址不是Flyweight了(后续添加的方法无法访问)
Player.prototype = {
  ...Flyweight,
  changeC() {
    //...
  },
};

// 精灵类
var Spirit = function (x, y, r) {
  this.x = x;
  this.y = y;
  this.r = r;
};
Spirit.prototype = Flyweight;
Spirit.prototype.changeR = function () {};

console.log(new Player(5, 2, "red").moveX(1).moveY(1));
代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值