js的设计模式简单整理

高阶函数

  1. 函数可以作为参数被传递
  2. 函数可以作为返回值输出
高阶函数实现 AOP

AOP(面向切面编程) 的主要作用是把一些跟核心业务逻辑无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括:日志统计,安全控制,异常处理等。

抽离出来之后,再通过‘动态织入’的方式渗入到业务模块中。

这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。

在 JavaScript 中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中。demo 查看:

Function.prototype.before = function(beforeFn) {
  let _self = this; // 保存原函数的引用
  return function() {
    // 返回包含了原函数和新函数的‘代理函数’
    beforeFn.apply(this, arguments); // 执行新函数,修正this
    return _self.apply(this, arguments); // 执行原函数
  };
};

Function.prototype.after = function(afterFn) {
  let _self = this;
  return function() {
    var ret = _self.apply(this, arguments);
    afterFn.apply(this, arguments);
    return ret; // 返回一个函数
  };
};

var func = function() {
  console.log(2);
};

func = func
  .before(function() {
    console.log(1);
  })
  .after(function() {
    console.log(3);
  });
func(); // 1 2 3
柯里化 currying

函数柯里化 (function currying)又称 部分求值

一个 currying 的函数授信啊会接收一些参数,接收这些参数之后,该函数并不会立即求值,而是继续返回另一个函数,传入的参数在函数形成的闭包中保存起来。

待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。简单点说就是返回一个函数。demo 查看:

let cost = (function() {
  let args = [];
  return function() {
    if (arguments.length === 0) {
      let money = 0;
      for (let i = 0; i < args.length; i++) {
        money += args[i];
      }
      return money;
    } else {
      [].push.apply(args, arguments);
    }
  };
})();

cost(100); // 未真正求值
cost(100); // 未真正求值
cost(100); // 未真正求值

cost(); // 求值并输出 300

设计模式

单例模式

定义: 保证一个类仅有一个实例,并提供一个访问他的全局访问点。

单例模式的核心是确保只有一个实例,并提供全局访问。

let getSingle = function(fn) {
  let result;
  return function() {
    return result || (result = fn.apply(this, arguments));
  };
};

let createSingle1 = function() {
  console.log("createSingle1");
};
let createSingle2 = function() {
  console.log("createSingle2");
};
let single1 = getSingle(createSingle1);
let single2 = getSingle(createSingle2);

single1(); // createSingle1
single2(); // createSingle2
策略模式

定义:定义一系列的算法,把他们封装起来,并且使他们可以相互替换。

这句话如果说得更详细一点,就是:定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对 Context 发起请求的时候,Context 总是把请求委托给这些策略对象中间的某一个进行计算。

计算奖金、缓动动画、表单校验。

//策略模式封装薪酬计算
let salaryStratege = {
  S: function(salary) {
    return salary * 4;
  },
  A: function(salary) {
    return salary * 3;
  },
  B: function(salary) {
    return salary * 2;
  }
};
let calculateBonus = function(level, salary) {
  return salaryStratege[level](salary);
};

console.log(calculateBonus("S", 20000)); // 输出:80000
console.log(calculateBonus("A", 10000)); // 输出:30000

// 策略模式封装form表单

let formStratege = {
  isNonEmpty: function(value, errMsg) {
    // 不为空
    if (value === "") {
      return errMsg;
    }
  },
  minLength: function(value, length, errMsg) {
    // 限制最小长度
    if (value.length < length) {
      return errMsg;
    }
  },
  isMobile: function(value, errMsg) {
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
      // 手机号码格式
      return errorMsg;
    }
  }
};
formStratege["isNonEmpty"]("", "参数不能为空");
代理模式

定义: 代理模式是为一个对象提佛那个一个代用品或占位符,一遍控制对它的访问。

代理模式的关键是,当客户不方便直接访问一个兑现更或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。

保护代理和虚拟代理,虚拟代理实现 图片预加载,缓存代理,用高阶函数动态创建代理。

//保护代理和虚拟代理
var B = {
  receiveFlower: function(flower) {
    A.listenGoodMood(function() {
      // 监听 A 的好心情
      var flower = new Flower(); // 延迟创建 flower 对象
      A.receiveFlower(flower);
    });
  }
};
//虚拟代理实现图片预加载
var myImage = (function() {
  var imgNode = document.createElement("img");
  document.body.appendChild(imgNode);
  return {
    setSrc: function(src) {
      imgNode.src = src;
    }
  };
})();

var proxyImage = (function() {
  var img = new Image();
  img.onload = function() {
    myImage.setSrc(this.src);
  };
  return {
    setSrc: function(src) {
      myImage.setSrc(
        "https://desk-fd.zol-img.com.cn/t_s960x600c5/g4/M07/06/05/Cg-4y1TcgwGIcCW6ADOtF-oxuF8AAUn6wNDA_AAM60v722.jpg"
      );
      // 显示的图片为proxyImage入参的图片,覆盖了myImage设置的图片
      img.src = src;
    }
  };
})();
proxyImage.setSrc(
  "https://thumbs.dreamstime.com/t/dandelion-fields-trees-shape-heart-sunset-valentines-day-63832782.jpg"
);
//缓存代理
var mult = function() {
  console.log("开始计算乘积");
  var a = 1;
  for (var i = 0, l = arguments.length; i < l; i++) {
    a = a * arguments[i];
  }
  return a;
};
var proxyMult = (function() {
  var cache = {};
  return function() {
    var args = Array.prototype.join.call(arguments, ",");
    if (args in cache) {
      return cache[args];
    }
    return (cache[args] = mult.apply(this, arguments));
  };
})();
proxyMult(1, 2, 3, 4); // 输出:24
proxyMult(1, 2, 3, 4); // 输出:24
//用高阶函数动态创建代理
/**************** 计算乘积 *****************/
var mult = function() {
  var a = 1;
  for (var i = 0, l = arguments.length; i < l; i++) {
    a = a * arguments[i];
  }
  return a;
};

/**************** 计算加和 *****************/
var plus = function() {
  var a = 0;
  for (var i = 0, l = arguments.length; i < l; i++) {
    a = a + arguments[i];
  }
  return a;
};

/**************** 创建缓存代理的工厂 *****************/
var createProxyFactory = function(fn) {
  var cache = {};
  return function() {
    var args = Array.prototype.join.call(arguments, ",");
    if (args in cache) {
      return cache[args];
    }
    return (cache[args] = fn.apply(this, arguments));
  };
};

var proxyMult = createProxyFactory(mult),
  proxyPlus = createProxyFactory(plus);

console.log(proxyMult(1, 2, 3, 4)); // 输出:24
console.log(proxyMult(1, 2, 3, 4)); // 输出:24
console.log(proxyPlus(1, 2, 3, 4)); // 输出:10
console.log(proxyPlus(1, 2, 3, 4)); // 输出:10
迭代器模式

定义: 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

内部迭代器和外部迭代器

// 内部迭代器
let each = function(ary, callback) {
  for (let i = 0; i < ary.length; i++) {
    callback.call(ary[i], i, ary[i]); // 把下标和元素当做参数传给callback函数
  }
};
each([1, 2, 3], function(n, i) {
  console.log([n, i]); // [0,1] [1,2] [2,3]
});
// 外部迭代器
let Iterator = function(obj) {
  let current = 0;
  let next = function() {
    current += 1;
  };
  let isDone = function() {
    return current >= obj.length;
  };
  let getCurrItem = function() {
    return obj[current];
  };
  return {
    next,
    isDone,
    getCurrItem
  };
};
let compare = function(iterator1, iterator2) {
  while (!iterator1.isDone && !iterator2.isDone) {
    if (iterator1.getCurrItem !== iterator2.getCurrItem) {
      throw new Error("iterator1 和 iterator2 不相等");
    }
    iterator1.next();
    iterator2.next();
  }
  console.log("iterator1 和 iterator2 相等");
};
let iterator1 = Iterator([1, 2, 3]);
let iterator2 = Iterator([1, 2, 3]);

compare(iterator1, iterator2); // 输出:iterator1 和 iterator2 相等
发布订阅模式

定义: 又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

let event = {
  clientObj: {}, // 存放缓存列表的对象
  listen: function(key, fn) {
    // 发布
    if (!this.clientObj[key]) {
      this.clientObj[key] = []; // 初始化缓存列表
    }
    this.clientObj[key].push(fn); // 将订阅的消息添加进缓存列表
  },
  trigger: function() {
    // 订阅
    let key = Array.prototype.shift.call(arguments), // (1);
      fns = this.clientObj[key]; //  找到key对应的缓存列表
    if (!fns || fns.length === 0) {
      return false;
    } else {
      for (let i = 0, fn; (fn = fns[i++]); ) {
        fn.apply(this, arguments); // (2) arguments 是 trigger 时带上的参数
      }
    }
  },
  remove: function(key, fn) {
    let fns = this.clientObj[key];
    if (!fns) {
      // 如果 key 对应的消息没有被人订阅,则直接返回
      return false;
    }
    if (!fn) {
      // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
      fns && (fns.length = 0);
    }
    // 反向遍历订阅的回调函数列表
    for (var l = fns.length - 1; l >= 0; l--) {
      var _fn = fns[l];
      if (_fn === fn) {
        fns.splice(l, 1); // 删除订阅者的回调函数
      }
    }
  }
};

event.listen("getName", name => {
  console.log("my name is " + name);
});

event.trigger("getName", "kk");

event.remove("getName", name => {
  console.log("my name is " + name);
});

你也可以这样写:

//自定义事件
var salesOffices = {}; // 定义售楼处
salesOffices.clientList = {}; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function(key, fn) {
  if (!this.clientList[key]) {
    // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
    this.clientList[key] = [];
  }
  this.clientList[key].push(fn); // 订阅的消息添加进消息缓存列表
};
salesOffices.trigger = function() {
  // 发布消息
  var key = Array.prototype.shift.call(arguments), // 取出消息类型
    fns = this.clientList[key]; // 取出该消息对应的回调函数集合
  if (!fns || fns.length === 0) {
    // 如果没有订阅该消息,则返回
    return false;
  }
  for (var i = 0, fn; (fn = fns[i++]); ) {
    fn.apply(this, arguments); // (2) // arguments 是发布消息时附送的参数
  }
};
salesOffices.listen("squareMeter88", function(price) {
  // 小明订阅 88 平方米房子的消息
  console.log("价格= " + price); // 输出: 2000000
});
salesOffices.listen("squareMeter110", function(price) {
  // 小红订阅 110 平方米房子的消息
  console.log("价格= " + price); // 输出: 3000000
});
salesOffices.trigger("squareMeter88", 2000000); // 发布 88 平方米房子的价格
salesOffices.trigger("squareMeter110", 3000000); // 发布 110 平方米房子的价格
命令模式

命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。

此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

菜单程序,撤消命令,命令队列。

<button id="button1">点击按钮 1</button>
<button id="button2">点击按钮 2</button>
<button id="button3">点击按钮 3</button>
// 菜单程序
function setCommond(button, commond) {
  button.onclick = function() {
    commond.execute();
  };
}

let MenuBar = {
  refresh: function() {
    console.log("刷新菜单目录");
  }
};
let subBar = {
  add: function() {
    console.log("添加子菜单");
  },
  del: function() {
    console.log("删除子菜单");
  }
};

let RefreshMenuBarCom = function(resiver) {
  this.resiver = resiver;
};
RefreshMenuBarCom.prototype.execute = function(resiver) {
  this.resiver.refresh();
};
let AddSubBarCom = function(resiver) {
  this.resiver = resiver;
};
AddSubBarCom.prototype.execute = function(resiver) {
  this.resiver.add();
};
let DelSubBarCom = function(resiver) {
  this.resiver = resiver;
};
DelSubBarCom.prototype.execute = function(resiver) {
  this.resiver.del();
};

let refreshMenuBarCom = new RefreshMenuBarCom(MenuBar);
let addSubBarCom = new RefreshMenuBarCom(subBar);
let delSubBarCom = new RefreshMenuBarCom(subBar);

setCommond(button1, refreshMenuBarCom); // 刷新菜单目录
setCommond(button2, addSubBarCom); // 添加子菜单
setCommond(button3, delSubBarCom); // 删除子菜单
组合模式

定义:将对象组合成树形结构,以表示”部分-整体“的层次结构。

宏命令,扫描文件夹。

<button id="button">按我</button>
// 宏命令
let MacroCommond = {
  commondList: [],
  add: function(commond) {
    this.commondList.push(commond);
  },
  execute: function() {
    // for循环的新写法
    for (let i = 0, commond; (commond = this.commondList[i++]); ) {
      commond.execute();
    }
  }
};

let openAcCom = {
  execute: function() {
    console.log("打开空调");
  }
};
/**家里的电视和音响是连接在一起的,所以可以用一个宏命令来组合打开电视和打开音响的命令*/
let openTvCom = {
  execute: function() {
    console.log("打开电视");
  }
};
let openSoundCom = {
  execute: function() {
    console.log("打开音响");
  }
};
let macroCommond1 = MacroCommond();
macroCommond1.add(openTvCom);
macroCommond1.add(openSoundCom);

/*********开门,关闭电脑的命令****************/
let openDoorCom = {
  execute: function() {
    console.log("打开门");
  }
};
let closePcCom = {
  execute: function() {
    console.log("关闭电脑");
  }
};
let macroCommond2 = MacroCommond();
macroCommond2.add(openDoorCom);
macroCommond2.add(closePcCom);

/*********现在把所有的命令组合成一个“超级命令”**********/
let macroCommond = MacroCommond();
macroCommond.add(openAcCom);
macroCommond.add(macroCommond1);
macroCommond.add(macroCommond2);

/*********最后给遥控器绑定“超级命令”**********/
let setCommond = (function(commond) {
  document.getElementById("button").onclick = function() {
    commond.execute();
  };
})(macroCommond);
模板方法模式

模板方法模式是一种只需使用继承就可以实现的非常简单的模式。

模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。

在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来实现。这也很好地体现了泛化的思想。

Coffee or Tea,钩子方法,好莱坞原则。

let Beverage = function(params) {
  let boilWater = function() {
    console.log("把水煮沸");
  };
  let brew =
    params.brew ||
    function() {
      throw new Error("必须传递brew");
    };
  let pourInCup =
    params.pourInCup ||
    function() {
      throw new Error("必须传递pourInCup");
    };
  let addCondiments =
    params.addCondiments ||
    function() {
      throw new Error("必须传递addCondiments");
    };
  let F = function() {};
  F.prototype.init = function() {
    boilWater();
    brew();
    pourInCup();
    addCondiments();
  };
  return F;
};

let Coffee = Beverage({
  brew: function() {
    console.log("用沸水冲泡咖啡");
  },
  pourInCup: function() {
    console.log("把咖啡倒进杯子");
  },
  addCondiments: function() {
    console.log("加糖和牛奶");
  }
});

let Tea = Beverage({
  brew: function() {
    console.log("用沸水冲泡茶");
  },
  pourInCup: function() {
    console.log("把茶倒进杯子");
  },
  addCondiments: function() {
    console.log("加柠檬");
  }
});

let coffee = new Coffee();
coffee.init(); // 把水煮沸 用沸水冲泡咖啡 把咖啡倒进杯子 加糖和牛奶
let tea = new Tea();
tea.init(); // 把水煮沸 用沸水冲泡茶 把茶倒进杯子 加柠檬

模板方法模式充分的体现了“好莱坞”原则。IOC是Inversion of Control的简称,

IOC的原理就是基于好莱坞原则,所有的组件都是被动的(Passive),所有的组件初始化和调用都由容器负责。

所有的framework都是遵循好莱坞原则设计的,否则就不叫framework。framework使用IoC的目的:
  
1. 对基于接口编程的支持
2. 减少单件和抽象工厂的依赖
3. 降低业务和框架的耦合
4. 业务组件可复用,可插拔

享元模式

定义: 享元(flyweight)模式,fly(苍蝇),意思是蝇量级。

享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

通用结构,通用对象池。

还有些未纳入的,之后总结。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值