欢迎点击关注-资深前端进阶:前端登顶之巅-最全面的前端知识点梳理总结
javascript设计模式
1.单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点
- 惰性单例 vue v-if 当需要时创建 不需要的时候可以选择隐藏dom而不是删除
2.策略模式 重点
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案
<html>
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post"> 请输入用户名:<input type="text" name="userName" />
请输入密码:<input type="text" name="password" /> 请输入手机号码:<input type="text" name="phoneNumber" /> <button>提交</button>
</form>
<script>
/***********************策略对象**************************/
var strategies = {
isNonEmpty: function (value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
minLength: function (value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
isMobile: function (value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
};
/***********************Validator 类**************************/
var Validator = function () {
this.cache = [];
};
Validator.prototype.add = function (dom, rules) {
var self = this;
for (var i = 0, rule; rule = rules[i++];) {
(function (rule) {
var strategyAry = rule.strategy.split(':');
var errorMsg = rule.errorMsg;
self.cache.push(function () {
var strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategies[strategy].apply(dom, strategyAry);
});
})(rule)
}
2
};
Validator.prototype.start = function () {
for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
var errorMsg = validatorFunc();
if (errorMsg) {
return errorMsg;
}
}
};
/***********************客户调用代码**************************/
var registerForm = document.getElementById('registerForm');
var validataFunc = function () {
var validator = new Validator();
validator.add(registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:6',
errorMsg: '用户名长度不能小于 10 位'
}]);
validator.add(registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密码长度不能小于 6 位'
}]);
validator.add(registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}]);
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function () {
var errorMsg = validataFunc();
if (errorMsg) {
alert(errorMsg);
return false;
}
}
</script>
</body>
</html>
3.代理模式
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return function (src) {
imgNode.src = src;
}
})();
var proxyImage = (function () {
var img = new Image;
img.onload = function () {
myImage(this.src);
}
return function (src) {
myImage('file:// /C:/Users/svenzeng/Desktop/loading.gif');
img.src = src;
}
})();
proxyImage('http:// imgcache.qq.com/music// N/k/000GGDys0yA0Nk.jpg');
4.迭代器模式
提供循环访问一个聚合对象中每个元素的方法,而又不暴露该对象内部
- 内部迭代器 (函数内部处理,外部调用一次,逻辑交给内部,缺点不灵活,优点使用简单)
- 外部迭代器 (函数内部暴露出相应方法,外部调用方法迭代,缺点增加调用复杂度,优点灵活性)
5.发布-订阅模式(观察者模式)
定义对象间一种一对多的依赖关系,当一个对象状态发生改变,所有依赖与他的对象得到通知 事件模型代替传统发布订阅 如点击事件 监听代表订阅 当点击后发布
不关心发布者和订阅者
var Event = (function () {
var clientList = {},
listen,
trigger, remove;
listen = function (key, fn) {
if (!clientList[key]) {
clientList[key] = [];
}
clientList[key].push(fn);
};
trigger = function () {
var key = Array.prototype.shift.call(arguments),
}
fns = clientList[key];
if (!fns || fns.length === 0) {
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);
}
remove = function (key, fn) {
var fns = clientList[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1);
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
Event.listen('squareMeter88', function (price) {
console.log('价格= ' + price);
});
Event.trigger('squareMeter88', 2000000);
发布订阅不一定先订阅在发布 可以先发布缓存下来等第一次订阅的时候在发布出来 qq离线缓存
全局事件的命名冲突 设计模式121页
6.命令模式
-
传统命令模式
请求发送者和请求接收者解耦,用命令对象存储相应命令 用命令类封装命令对象相应方法,将命令对象传入命令类得到请求接收者
有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请 求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接 收者能够消除彼此之间的耦合关系
-
javascript命令模式
函数闭包实现
var setCommand = function (button, func) { button.onclick = function () { func(); } }; var MenuBar = { refresh: function () { console.log('刷新菜单界面'); } }; var RefreshMenuBarCommand = function (receiver) { return function () { receiver.refresh(); } }; var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar); setCommand(button1, refreshMenuBarCommand);
命令模式可以很好地时间撤销重做功能
命令队列
7.组合模式
将命令模式的宏命令组合成一个对象 有点像发布订阅模式 组合对象和叶对象
组合模式最大的优点在于可以一致地对待组合对象和基本对象。客户不需要知道 当前处理的是宏命令还是普通命令,只要它是一个命令,并且有 execute 方法,这个命令就可以 被添加到树中
组合模式如果运用得当,可以大大简化客户的代码。一般来说,组合模式适用于以下这两种
情况。
- 表示对象的部分整体层次结构。组合模式可以方便地构造一棵树来表示对象的部分整 体结构。特别是我们在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最 终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模 式中增加和删除树的节点非常方便,并且符合开放封闭原则。
- 客户希望统一对待树中的所有对象。组合模式使客户可以忽略组合对象和叶对象的区别, 客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就 不用写一堆 if、else 语句来分别处理它们。组合对象和叶对象会各自做自己正确的事情, 这是组合模式最重要的能力。
8.模板方法模式
多用于生命周期 给一定生命周期 使用者在生命周期做操作
钩子 可以在模板init中加入一些要变化的钩子 当使用者需要的时候在加入比如放糖和牛奶
var Beverage = function () {};
Beverage.prototype.boilWater = function () {
console.log('把水煮沸');
};
Beverage.prototype.brew = function () {
throw new Error('子类必须重写 brew 方法');
};
Beverage.prototype.pourInCup = function () {
throw new Error('子类必须重写 pourInCup 方法');
};
Beverage.prototype.addCondiments = function () {
throw new Error('子类必须重写 addCondiments 方法');
};
Beverage.prototype.customerWantsCondiments = function () {
return true; // 默认需要调料
};
Beverage.prototype.init = function () {
this.boilWater();
this.brew();
this.pourInCup();
if (this.customerWantsCondiments()) {
this.addCondiments();
}
};
var CoffeeWithHook = function () {};
CoffeeWithHook.prototype = new Beverage();
CoffeeWithHook.prototype.brew = function () {
console.log('用沸水冲泡咖啡');
};
// 如果挂钩返回 true,则需要调料
CoffeeWithHook.prototype.pourInCup = function () {
console.log('把咖啡倒进杯子');
};
CoffeeWithHook.prototype.addCondiments = function () {
console.log('加糖和牛奶');
};
CoffeeWithHook.prototype.customerWantsCondiments = function () {
return window.confirm('请问需要调料吗?');
};
var coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.init();
9.享元模式
享元模式的核心是运用共享技术来有效支持大量细粒度的对象,减少对象的使用,间谍内存分配.主要解决大量对象带来的性能问题,减少重复的内部或外部变量创建,比如:根据不同type创建不同类,但是会有相同type 如果相同返回之前创建的对象类
10.职责链模式
一系列可能会处理请求的对象被连接成一条链,请求在这些对 象之间依次传递,直到遇到一个可以处理它的对象
例子: 如果早高峰能顺利挤上公交车的话,那么估计这一天都会过得很开心。因为公交车上人 实在太多了,经常上车后却找不到售票员在哪,所以只好把两块钱硬币往前面递。除非 你运气够好,站在你前面的第一个人就是售票员,否则,你的硬币通常要在 N 个人手上 传递,才能最终到达售票员的手里。
- 优点
- 链中的节点对象可以灵活地拆分重组。增加或者删除一个节 点,或者改变节点在链中的位置都是轻而易举的事情
- 手动指定起始节点,请求并不是非得从链中的第一个 节点开始传递
- 缺点
不能保证某个请求一定会被链中的节点处理,需要保底函数
使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分 节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避 免过长的职责链带来的性能损耗。
11.中介者模式
对于拆分细的可复用对象产生的紧密联系关系中,能有效减少各个对象之间的耦合与关联性,通过一个观察对象,来处理各个对象改变的状态在通知下去
12.装饰着模式
装饰者模式的作用就是为对 象动态加入行为。用于一开始不能确 定对象的全部功能时
代理模式当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。代理模式强调一种关系(Proxy 与它的实体之间的关系),这种关系 可以静态的表达,也就是说,这种关系在一开始就可以被确定。
AOP装饰函数
Function.prototype.before = function( beforefn ){
var __self = this; // 保存原函数的引用
return function(){ // 返回包含了原函数和新函数的"代理"函数
beforefn.apply( this, arguments ); // 执行新函数,且保证 this 不被劫持,新函数接受的参数 // 也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply( this, arguments ); // 执行原函数并返回原函数的执行结果, 2 // 并且保证 this 不被劫持
}
}
Function.prototype.after = function( afterfn ){ var __self = this;
return function(){
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
} };
AOP动态改变函数的参数 token 表单验证
var func = function( param ){
console.log( param ); // 输出: {a: "a", b: "b"}
}
func = func.before( function( param ){
param.b = 'b';
});
func( {a: 'a'} );
13.状态模式 ✨
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变
https:// github.com/jakesgordon/ javascript-state-machine
var Light = function(){
this.currState = FSM.off; // 设置当前状态 this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement( 'button' ),
self = this;
button.innerHTML = '已关灯';
this.button = document.body.appendChild( button );
this.button.onclick = function(){
self.currState.buttonWasPressed.call( self );
}
};
var FSM = {
off: {
buttonWasPressed: function(){
console.log( '关灯' );
this.button.innerHTML = '下一次按我是开灯';
this.currState = FSM.on;
}
},
on: {
buttonWasPressed: function(){
console.log( '开灯' );
this.button.innerHTML = '下一次按我是关灯';
this.currState = FSM.off;
};
}
}
var light = new Light();
light.init();
场景
- 另一个状态模式示例——文件上传
状态模式的优缺点
- 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态 类,很容易增加新的状态和转换
- 避免 Context 无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了 Context 中原本过 5 多的条件分支
- 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然
- Context 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响
- 状态模式的缺点是会在系统中定义许多状态类,编写 20 个状态类是一项枯燥乏味的工作, 而且系统中会因此而增加不少对象。另外,由于逻辑分散在状态类中,虽然避开了不受欢迎的条 件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑。
14.适配器模式
- 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实 现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够 使它们协同作用。
- 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象 增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理 模式是为了控制对对象的访问,通常也只包装一次
- 外观模式的作用倒是和适配器比较相似,有人把外观模式看成一组对象的适配器,但外 观模式最显著的特点是定义了一个新的接口
第三部分
设计原则和编程技巧
单一职责原则
SRP 原则体现为:一个对象(方法)只做一件事情。
SRP 原则的优缺点
- SRP 原则的优点是降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度, 这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他 的职责。
- SRP 原则也有一些缺点,最明显的是会增加编写代码的复杂度。当我们按照职责把对象 分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。
最少知识原则 LKP 迪米特法则
最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不 必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对 象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三 者对象来转发这些请求。
某军队中的将军需要挖掘一些散兵坑。下面是完成任务的一种方式:将军可以通知 上校让他叫来少校,然后让少校找来上尉,并让上尉通知一个军士,最后军士唤来一个 士兵,然后命令士兵挖掘一些散兵坑。
- 外观模式 : 外观模式的作用是对客户屏蔽一组子系统的复杂性
假设我们在编写这个老式洗衣机的程序,客户至少要和浸泡、洗衣、漂洗、脱水这 4 个子系 统打交道。如果其中的一个子系统发生了改变,那么客户的调用代码就得发生改变。而通过外观 将客户和这些子系统隔开之后,如果修改子系统内部,只要外观不变,就不会影响客户的调用。
- 中介者模式
中介者模式很好地体现了最少知识原则。通过增加一个中介者对象,让所有的相关对象都通 过中介者对象来通信,而不是互相引用。所以,当一个对象发生改变时,只需要通知中介者对象 即可。
- 封装
开放-封闭原则 OCP
当需要改变一个程序的功能或者给这个程序增加新功 能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码 !!!最明显的就是找出程序 中将要发生变化的地方,然后把变化封装起来
每当我们看到一大片的 if 或者 swtich-case 语句时,第一时间就应该考虑,能 否利用对象的多态性来重构它们. 利用多态的思想,我们把程序中不变的部分隔离出来,然后把可变的部分封装起来.
var makeSound = function( animal ){
animal.sound();
};
var Duck = function(){};
Duck.prototype.sound = function(){
console.log( '嘎嘎嘎' );
};
var Chicken = function(){};
Chicken.prototype.sound = function(){
console.log( '咯咯咯' );
};
makeSound( new Duck() ); // 嘎嘎嘎
makeSound( new Chicken() ); // 咯咯咯
/********* 增加动物狗,不用改动原有的 makeSound 函数 ****************/
var Dog = function(){};
Dog.prototype.sound = function(){
console.log( '汪汪汪' );
};
makeSound( new Dog() ); // 汪汪汪
符合开放-封闭原则的几种用法
- 多态
- 放置挂钩
- 回调函数