Skip to content

《JavaScript设计模式》结构型设计模式

外观模式

为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口的访问更容易。在JavaScript中有时也会用于对底层结构兼容性做统一的封装来简化用户使用

js
var getEvent = function (event) {
   //标准浏览器返回event,IE返回window.event
   return event || window.event;
 }
 var getTarget = function (event) {
   var event = getEvent(event);
   //标准浏览器返回target,IE返回srcElement
   return event.target || event.srcElement;
 }
 var preventDefault = function (event) {
   var event = getEvent(event);
   //标准浏览器
   if (event.preventDefault) {
     event.preventDefault();
   } //IE浏览器
   else {
     event.returnValue = false;
   }
 }
 document.onclick = function (e) {
   //阻止默认行为
   preventDefault(e);
   //获取事件源目标对象
   if (getTarget(e) !== document.getElementById('btn')) {
     console.log("阻止");
   }
 }

适配器模式

将一个类(对象)的接口(方法或属性)转化成为另外一个接口,使类(对象)之间接口的不兼容问题通过适配器得以解决

适配不同框架

js
var NewJQuery = {
  $: function (id) {
    return document.getElementById(id);
  },
  css: function (id, key, value) {
    this.$(id).style[key] = value;
  }
}
NewJQuery.$ = function (id) {
  return $("#" + id);
}
NewJQuery.css = function (id, key, value) {
  return $("#" + id).css(key, value);
}
console.log(NewJQuery.$("id"));
NewJQuery.css("id", "background-color", "red");

参数适配

js
function doSomeThing(obj) {
  var _adapter = {
    id: 1,
    name: "Tom",
    age: 20,
    sex: "男",
    hobby: "篮球"
  }
  for (var i in _adapter) {
    _adapter[i] = obj[i] || _adapter[i];
  }
  return _adapter;
}

数据适配

js
function arrToObjAdapter(arr) {
  return {
    name: arr[0],
    age: arr[1]
  }
}
arrToObjAdapter(['Tom', 21])

代理模式

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>

  <body>
    <iframe name="proxyIframe" id="proxyIframe" src=""> </iframe>
    <form
      action="http://localhost:51410/home/index"
      method="post"
      target="proxyIframe"
    >
      <input type="text" name="callback" value="callback" />
      <input type="text" name="proxy" value="http://127.0.0.1:5500/b.html" />
      <input type="submit" value="提交" />
    </form>
    <script type="text/JavaScript">
      function callback(data){
        console.log('成功接收数据', data);
      }
    </script>
  </body>
</html>
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script type="text/JavaScript">
        //页面加载后执行
     window.onload = function(){
       //如果不在A页面中返回,不执行
       if(top == self) return;
       //获取并解析searcher中的数据
       var arr = location.search.substr(1).split('&'),
       //预定义函数名称以及参数集
         fn, args;
       for(var i = 0, len = arr.length, item; i < len; i++){
         //解析searcher中的每组数据
         item = arr[i].split('=');
         //判断是否为回调函数
         if(item[0] == 'callback'){
           //设置回调函数
           fn = item[1];
         //判断是否是参数集
         }else if(item[0] == 'arg'){
           //设置参数集
           args = item[1];
         }
       }
       try{
         //执行A页面中预设的回调函数
         eval('top.' + fn + '("' + args + '")');
       }catch(e){}
     }
     </script>
</body>

</html>
csharp
public ActionResult Index()
{
    HttpContext.Response.StatusCode = 302;
    var url = HttpContext.Request["proxy"] + "?callback=" + HttpContext.Request["callback"] + "&arg=success";
    HttpContext.Response.RedirectLocation = url;
    return View();
}

装饰者模式

在不改变原对象的基础上,通过对其进行过包装拓展(添加属性高或者方法)使原有对象可以满足用户的更复杂需求

js
var decorator = function (input, fn) {
  //获取事件源
  var input = document.getElementById(input);
  //判断事件源是否绑定事件
  if (typeof input.onclick === 'function') {
    //缓存事件源原有回调函数
    var oldClickFn = input.onclick;
    //为事件源定义新的事件
    input.onclick = function () {
      //事件源原有回调函数
      oldClickFn();
      //新增的回调函数
      fn();
    }
  } else {
    //如果事件源未绑定,直接为事件源添加新增回调函数
    input.onclick = fn;
  }
}
decorator('id', function () {
  console.log(1);
});

桥接模式

在系统沿着多个维度变化的同时,又不增加其复杂度并已达到解耦

js
var spans = document.getElementsByTagName('span');
spans[0].onmouseover = function () {
  this.style.color = 'red';
  this.style.background = '#ddd';
}
spans[0].onmouseout = function () {
  this.style.color = '#333';
  this.style.background = '#f5f5f5';
}
function changeColor(dom, color, bg) {
  dom.style.color = color;
  dom.style.background = bg;
}
var spans = document.getElementsByTagName('span');
spans[0].onmouseover = function () {
  changeColor(this, 'red', '#ddd');
}
spans[0].onmouseout = function () {
  changeColor(this, '#333', '#f5f5f5');
}

组合模式

又称部分-整体模式,将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性

js
function inheritObject(o) {
  //声明一个过渡函数对象
  function Obj() {}
  //过渡对象的原型继承父对象
  Obj.prototype = o;
  //返回过渡对象的一个实例,该实例的原型继承了父对象
  return new Obj();
}
function inheritPrototype(subClass, superClass) {
  //复制一份父类的原型保存在变量中
  var parent = inheritObject(superClass.prototype);
  //修正因为重写子类原型导致子类的constructor属性被修改
  parent.constructor = subClass;
  //设置子类的原型
  subClass.prototype = parent;
}
var News = function () {
  //子组件容器
  this.children = [];
  //当前组件元素
  this.element = null;
}
News.prototype = {
  init: function () {
    throw new Error("方法必须重写!");
  },
  add: function () {
    throw new Error("方法必须重写!");
  },
  getElement: function () {
    throw new Error("方法必须重写!");
  }
}
var Container = function (id, parent) {
  //构造函数继承父类
  News.call(this);
  //模块ID
  this.id = id;
  //模块的父容器
  this.parent = parent;
  //构建初始化方法
  this.init();
}
inheritPrototype(Container, News);
Container.prototype.init = function () {
  this.element = document.createElement('ul');
  this.element.id = this.id;
  this.element.className = 'articles-container'
}
Container.prototype.add = function (child) {
  //在子元素容器中插入子元素
  this.children.push(child);
  //插入当前组件元素树中
  this.element.appendChild(child.getElement());
  return this;
}
Container.prototype.getElement = function () {
  return this.element;
}
Container.prototype.show = function () {
  this.parent.appendChild(this.element);
}
var Item = function (className) {
  News.call(this);
  this.className = className || "";
  this.init();
}
inheritPrototype(Item, News);
Item.prototype.init = function () {
  this.element = document.createElement('li');
  this.element.className = this.className;
}
Item.prototype.add = function (child) {
  //在子元素容器中插入子元素
  this.children.push(child);
  //插入当前组件元素树中
  this.element.appendChild(child.getElement());
  return this;
}
Item.prototype.getElement = function () {
  return this.element;
}
//图片文章列表
var ImageNews = function (url, href, className) {
  News.call(this);
  this.url = url || "";
  this.href = href || "#";
  this.className = className || "normal";
  this.init();
}
inheritPrototype(ImageNews, News);
ImageNews.prototype.init = function () {
  this.element = document.createElement('a');
  var img = new Image();
  img.src = this.url;
  this.element.appendChild(img);
  this.element.className = 'image-news' + this.className;
  this.element.href = this.href;
}
ImageNews.prototype.add = function () {}
ImageNews.prototype.getElement = function () {
  return this.element;
}
//文字文章列表
var TextNews = function (text, href) {
  News.call(this);
  this.text = text || "";
  this.href = href || "#";
  this.init();
}
inheritPrototype(TextNews, News);
TextNews.prototype.init = function () {
  this.element = document.createElement('a');
  this.element.innerHTML = this.text;
  this.element.href = this.href;
  this.element.className = "text";
}
TextNews.prototype.add = function () {}
TextNews.prototype.getElement = function () {
  return this.element;
}
var newObj = new Container('articles', document.body);
newObj.add(new Item('normal').add(
    new ImageNews('img/HBuilder.png', '#', 'small')
  )
  .add(new Item('normal').add(
    new ImageNews('img/HBuilder.png', '#', 'small')
  ))
  .add(new Item('normal').add(
    new TextNews('测试列表', '#')
  ))
  .add(new Item('normal').add(
    new TextNews('测试列表2', '#')
  ))
).show();

享元模式

运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销

js
// 享元对象
var Flyweight = function () {
  var created = [];
  function create() {
    var dom = document.createElement('div');
    document.getElementById('container').appendChild(dom);
    created.push(dom);
    return dom;
  }
  return {
    getDiv: function () {
      if (created.length < 5) {
        return create();
      } else {
        var div = created.shift();
        created.push(div);
        return div;
      }
    }
  }
}();
// 实现需求
var paper = 0,
  num = 5,
  len = article.length;
for (var i = 0; i < 5; i++) {
  if (article[i]) {
    Flyweight.getDiv().innerHTML = article[i];
  }
}
document.getElementById('next_page').onclick = function () {
  if (article.length < 5) {
    return
  }
  var n = ++paper * num % len,
    j = 0;
  for (; j < 5; j++) {
    if (article[n + j]) {
      Flyweight.getDiv().innerHTML = article[n + j];
    } else if (article[n + j - len]) {
      Flyweight.getDiv().innerHTML = article[n + j - len];
    } else {
      Flyweight.getDiv().innerHTML = "";
    }
  }
}