文章目录
JS设计模式
1.单例模式(单体模式)
- 到底什么是单例模式?单例模式的定义是保证一个类仅有一个实例。并提供一个访问它的全局访问点。
- 单例模式呢是一种常见的模式。有一些对象我们往往只需要一个,比如说,线程池。全局的缓存浏览器当中的 window 对象。等等。很多东西都是这个样子。在 sill 的这个开发当中。单例模式的用途呢,其实比较广泛。比如说当我们单击登录按钮的时候,页面当中是不是只会出现一个登录浮窗就是登录弹窗。而这个登录弹窗是唯一的。无论单击多少次登录按钮这个创那这个弹窗都只会被创造建一次,这个登录弹窗我们就适合使用单例模式来创建
- 代码
1.1 简单单例模式
/要实现一个标准的单例模式呢。不复杂。无非是用一个变量来去标识当前是否已经为某一个类创建过对象,如果已经创建过,则在下一次获取该类的实例对象时,直接返回之前创建的对象。/
var Person=function(name){
this.name=name;
this.instance=null;
}
Person.prototype=sayHi=function(){
console.log(`你好,我叫${this.name}`)
}
Person.getInstace=function(name){
if(!this.instace){
this.instace=new Person(name);
}
return this.instace;
}
var Person=function(name){
this.name=name;
this.instance=null;
}
Person.prototype=sayHi=function(){
console.log(`你好,我叫${this.name}`)
}
Person.getInstace=(function(){
var instance=null;
// function aa(name){
// if(!instance){
// instance=new Person(name);
// }
// return instance;
// }
// return aa();
return function(name){
if(!instance){
instance=new Person(name);
}
return instance;
}
})()
- 我们通过Person.getInstance来获取person类的唯一对象。这种方式呢相对简单,但有一个问题。就是增加了这个类的不透明性。Person类的使用者必须得知道这是一个单例类,和以往我们通过new xxx的方式来获取对象不同。必须这里要使用Person.getInstance来获取对象。
1.2透明的单例模式
- 所以我们现在的目标是实现一个透明的单例类。用户可以从这个类当中创建对象的时候,可以像使用其他任何普通的构造函数类一样调用。
var CreateDiv=(function(){
var instance;
var CreateDiv_=function(html){
if(instance){
return instance;
}
this.html=html;
this.init();
return instance=this;
}
CreateDiv_.prototype.init=function(){
var div=document.createElement('div');
div.innerHTML=this.html;
document.body.appendChild(div)
}
return CreateDiv_;
})();
var a=new CreateDiv('aaaaa');
var b=new CreateDiv('bbbbb');
- 现在这种实现方式。Create DIV的构造函数实际上负责了两种两件事情。是创建对象和执行初始化的诶,那可方法。第二是保证只有一个对象。虽然到目前为止我们还没有接触过。嗯,有个概念叫单一职责原则。但是可以明确一点,就是这种方式呢可扩展性不强。并且看起来这个构造函数就变得很奇怪。
- 假如突然我们有一个新的需求,是利用这个类在页面当中创建千千万万个DIV。也就是说让这个类从现有的单例类变成一个普通的可以产生多个实例的类的话,你看你现在这种方式能实现吗?不能。所以我们就必须得改写刚刚的createDiv 构造函数,控制创建唯一对象的那一段去掉。这种修改。会给我们带来很多不必要的麻烦。不必要的操作。
1.3用代理现实单例模式
- 我们通过引入代理类的方式来解决上面提到的这些问题。
var CreateDiv=function(html){
this.html=html;
}
CreateDiv.prototype.init=function(){
var div=document.createElement('div');
div.innerHTML=this.html;
document.body.appendChild(div);
}
// 引入一个代理类来去解决这个问题。proxyCreateDiv;
var ProxyCreateDiv=(function(){
var instance;
return function(html){
if(!instance){
instance=new CreateDiv(html);
}
return instance;
}
})();
1.4 JavaScript 中的单例模式
前面提到的几种单例模式的实现。更多的是啊,接近于传统的面向对象语言的实现。比如说Java当中,如果需要某一个对象,就必须先得定义一个类。对象总是从那当中去创建而来的。因为java语言或者传统的面向对象语言中多包含类这个概念。而js语言当中不包含类这个概念。
也正是这样子。如果我们生搬硬套单例模式的概念,就没有任何意义。在js当中呢,创建对象的方法其实非常简单。既然我们需要一个唯一的对象,为什么要先创建一个类?直接用字面量创建对象的形式来去产生一个这样的对象不就可以了吗?
- 单例模式的核心是确保只有一个实例,并提供全局访问.
-
- 使用命名空间
var a={};
// 全局变量会导致全局环境的污染。
var huskyuncle={
a:function(){
},
b:function(){
}
}
var zhanpeng={
a:function(){
},
c:function(){
},
d:function(){
a();
},
instance:null,
createDiv:function(html){
// var div=document.createElement('div');
// div.innerHTML=html;
}
}
-
- 闭包封装私有变量
var user=(function(){
var name="范俊";
var age=38;
return {
getUserInfo:function(){
return name+"---"+age;
}
}
})()
1.5惰性单例
- 惰性单例指的是在需要的时候才创建实例对象。杜兴丹利是单例模式里边儿最重要的一点。这种技术啊,在实际的开发过程当中占很大的比例。当然也非常有用。所以呢,接下来我们写一个例子。
var createLoginLayer = (function() {
var instance = null;
return function() {
if (instance) {
return instance;
}
var div = document.createElement('div');
div.innerHTML = '我是登录浮窗';
div.style.display = "none";
div.style.width = "300px";
div.style.height = "180px";
div.style.border = "2px solid red";
div.style.textAlign = 'center';
div.style.fontWeight = '800';
var mask = document.getElementById('mask');
mask.appendChild(div);
instance = div;
return instance;
}
})();
var loginBtn = document.getElementById('loginBtn');
loginBtn.onclick = function() {
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
}
- 通用惰性单例
var getSingle = function(fn) {
var result;
return function() {
return result || (result = fn.apply(this, arguments));
}
}
var createLogin = function() {
var div = document.createElement('div');
div.innerHTML = '我是登录浮窗';
div.style.display = "none";
div.style.width = "300px";
div.style.height = "180px";
div.style.border = "2px solid red";
div.style.textAlign = 'center';
div.style.fontWeight = '800';
var mask = document.getElementById('mask');
mask.appendChild(div);
return div;
}
var createSingleLoginLayer = getSingle(createLogin);
var createSingleIframeLayer = getSingle(function() {
var div = document.createElement('iframe');
div.innerHTML = '我是登录浮窗';
div.style.display = "none";
div.style.width = "300px";
div.style.height = "180px";
div.style.border = "2px solid red";
div.style.textAlign = 'center';
div.style.fontWeight = '800';
var mask = document.getElementById('mask');
mask.appendChild(div);
return div;
})
// 我们上面写的这个例子当中,啊,我们把实例对象的职责和管理丹利的职责分别放在两个不同的方法里,这两个方法可以独立去变化。互相不影响。当他们连在一起去使用的时候,就完成了创建唯一实例对象的一个功能。
// 像上面这种单例模式的用途啊。不单单只能用来创建对象。举个例子你们听一下啊,比如说我们平时做的最多的一件事儿就是做呃数据列表的渲染。
- 小结:1.传统单例模式的实现。2.通用单例模式 3.惰性单例模式
2.策略模式
俗话说条条大路通罗马。其实在美剧这个越狱当中,
- 在现实中,很多时候也有很多种途径到达同一个目的地。比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行路线或方式。
- 如果没有时间,但是呢,不在乎钱,你可以选择坐飞机
- 如果没有时间,在穷一点,你可以选择坐大巴或者火车
- 如果再穷一点,你可以选择骑自行车
- 在程序设计当中,我们会经常遇到类似的情况,要实现某一个功能有多重方案可以去选择。比如一个压缩文件程序。我们可以选择使用相关的zip的算法,我们也可以选择使用gzip算法。
策略模式的定义是;定义一系列的算法,把他们一个一个的封装起来,并且使他们互相替换
2.1使用策略模式计算奖金
很多公司的年终奖是根据员工的工资基数,和年底绩效的情况发放的。例如,绩效为s的人。年终奖有四倍工资。绩效为c的人。年终奖有三倍工资。绩效为b的人。年终奖有两倍工资。假设财务部门要求我们提供一段代码来方便他们计算。员工的年终奖。
1.简单代码实现
var caulatorBonus=function(performanseLevel,salary){
if(performanseLevel==='s'){
return salary4
}
if(performanseLevel==='c'){
return salary3
}
if(performanseLevel==='b'){
return salary2
}
}
- 以上代码存在的一些问题:
- a.函数呢比较庞大。并且包含了很多if else的分支语句,这些语句需要覆盖你所有的逻辑分支。
- b.这个函数缺乏弹性,如果增加了一种新的绩效考核A。或者想把原来的s的奖金系数改为5。那我们必须得找到这个函数的内部实现,然后去修改它。本身可能大家觉得没有什么,但实际上各种各样的设计模式都有一些原则,对于我们来讲这样的东西啊,违反了开放-封闭的原则。
- c.算法的复用性差,如果在程序的其他地方需要重用这些计算的算法,我们只能复制粘贴。
2.组合函数重构代码
var performanseS=function(salary){
return salary4;
}
var performanseA=function(salary){
return salary3;
}
var performanseB=function(salary){
return salary2;
}
var performanseC=function(salary){
return salary;
}
var performanseD=function(salary){
return salary0.5;
}
var caculatorBonus=function(performanseLevel,salary){
if(performanseLevel=='S'){
return performanseS(salary);
}
if(performanseLevel=='A'){
return performanseA(salary);
}
}
- 以上代码是做了一些改善。但是改善有限。我们依然没有解决最重要的问题,就是函数有可能越来越庞大,而且在系统变化的时候,缺乏弹性.
3.使用策略模式重构代码
- 策略模式指的是定义一系列的算法,把他们一个一个封装起来。将不变的部分和变化的部分隔开。这才能符合我们的要求。策略模式的目的就是将算法的使用与算法的实现分开。我们要分离开来。
- 基于一个策略模式的程序至少由两部分组成。第一部分是一组策略类。策略类里边儿封装的是具体的算法,并且负责具体的计算过程。第二部分是什么是环境?环境呢,接受客户的请求。之后,然后把请求委托给策略类。去实现。你想要去做到这样一点啊,那就说明我们刚说过的环境类里面要维持对某一个策略对象的引用。你要一直保持这个引用。
var PerformanceS=function(){}
PerformanceS.prototype.caculate=function(salary){
return salary4;
}
var PerformanceA=function(){}
PerformanceA.prototype.caculate=function(salary){
return salary3;
}
var PerformanceD=function(){}
PerformanceA.prototype.caculate=function(salary){
return salary+1;
}
// Bonus:
var Bonus=function(){
this.salary=null;//原始的工资
this.strategy=null;// 绩效等级对应点 策略对象
}
Bonus.prototype.setStrategy=function(strategy){
this.strategy=strategy;
// 设置员工绩效等级对应的策略对象
}
Bonus.prototype.setSalary=function(salary){
this.salary=salary;
// 设置员工的原始工资
}
Bonus.prototype.getBonus=function(){
// 获得奖金数额
return this.strategy.caculate(this.salary);
// 把计算奖金的操作委托给对应的策略对象去执行。
}
// 定义一系列的算法。把他们各自封装成策略算法是被封装在内部的方法里的。如果我们外部对这个嗯,策略对象发起请求的时候,对象。会把请求委托给这些个策略对象中间的某一个进行计算。
var bonus=new Bonus();
bonus.setSalary(1000);
bonus.setStrategy(new PerformanceS());
bonus.getBonus();
2.2JavaScript 版本的策略模式
var strategies={
"S":function(salary){
return salary4;
}
"A":function(salary){
return salary3;
},
"B":function(salary){
return salary2;
},
"C":function(salary){
return salary1;
}
}
var caculatorBonus=function(level,salary){
return strategies[level](salary);
}
var a=caculatorBonus('S',3000);
4.使用策略模式实现表单验证
<form id="registerForm" action="">
<input type="text" name="username" placeholder="用户名">
<input type="text" name="password" placeholder="密码">
<input type="text" name="mobile" placeholder="手机号码">
<input type="button" id="saveBtn" value="注册">
</form>
// 不同规则的策对象
var strategies={
isNonEmpty:function(value,errorMsg){// 不为空
if(value==""){
return errorMsg;
}
},
minLength:function(value,length,errorMsg){// 限制最小长度
if(value.lenght<lenght){
return errorMsg;
}
},
isMobile:function(value,errorMsg){
if(/^1(3|4|5|7|8)\d{9}$/.test(value)){
return errorMsg;
}
}
}
// 验证类
var Validator=function(){
this.cache=[];
}
Validator.prototype.add=function(dom,rule,errorMsg){
var tmpArr=rule.split(':');// 把strategies和参数分开
this.cache.push(function(){
// 把校验的步骤用空函数包裹起来,放入cache
var strategy=tmpArr.shift();// 把用户的strategy规则挑选出来
tmpArr.unshift(dom.value); //把input的value添加至参数列表
tmpArr.push(errorMsg);// 把errorMsg添加至参数列表
return strategies[strategy].apply(dom,tmpArr)
});
}
Validator.prototype.start=dunction(){
for(var i=0,ValidatorFunc;ValidatorFunc=this.cache[i++]){
var msg=ValidatorFunc();//开始校验,并且取得校验后的错误信息
if(msg){
return msg;
}
}
}
// 获取表单;元素
var registerForm = document.getElementById('registerForm');
var ValidatorFunc=function(){
// 创建一个验证Validator对象
var Validator=new Validator();
// 添加一些校验规则
Validator.add(registerForm.username,'isNonEmpty','用户名不能为空');
Validator.add(registerForm.password,'minLength','密码长度不能为小于8位');
Validator.add(registerForm.mobile,'isMobile','手机号码格式不正确');
Validator.start();// 获取校验的结果
return errorMsg;// 返回校验的结果
}
var saveBtn=document.getElemtnById('saveBtn');
saveBtn.onclick=function(){
var errorMsg = validatorFunc();
if (errorMsg) {
console.log(errorMsg);
return false;
}
}
5.给某一个文本输入框添加多种校验规则
- 一个文本输入框只能对应一种校验规则。比如说用户名的输入框只能校验输入是否为空。但是却没有办法添加其他的校验规则。
// 不同规则的策对象
var strategies={
isNonEmpty:function(value,errorMsg){// 不为空
if(value==""){
return errorMsg;
}
},
minLength:function(value,length,errorMsg){// 限制最小长度
if(value.lenght<lenght){
return errorMsg;
}
},
isMobile:function(value,errorMsg){
if(/^1(3|4|5|7|8)\d{9}$/.test(value)){
return errorMsg;
}
}
}
/Validator类/
var Validator=function(){
this.cache=[];
}
Validator.prototype.add=function(dom,rules){
var self=this;
// 将每个表单元素 所要验证的规则添加至Validator 实例对象中
for(var i=0;i<rules.length;i++){
var rule=rules[i];
(function(OneRule){
var strategyArr=OneRule.strategy.split(':');
var errorMsg=OneRule.errorMsg;
self.cache.push(function(){
var strategy=strategyArr.shift();
strategyArr.unshift(dom.value);
strategyArr.push(errorMsg);
return strategies[strategy].apply(dom,strategyArr);
})
})(OneRule)
}
}
Validator.prototype.start=function(){
for(var i=0;i<this.cache.length;i++){
var ValidatorFn=this.cache[i];
var errorMsg=ValidatorFn();
console.log(errorMsg);
}
}
// 获取表单;元素
var registerForm = document.getElementById('registerForm');
/实例化了Validator实例对象,并向其中添加表单元素的调用方式/
var ValidatorFunc=function(){
var Validator=new Validator();
// 一个元素要应用多个规则
validator.add(registerForm.username, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:6',
errorMsg: '用户名不能小于6位'
}]);
validator.add(registerForm.password, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:12',
errorMsg: '密码不能小于12位'
}]);
validator.add(registerForm.mobile, [{
strategy: 'isNonEmpty',
errorMsg: '手机号不能为空'
}, {
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}]);
// 开始验证 当前规则
var errorMsg = validator.start();
// return errorMsg;
}
3.代理模式
- 代理模式呢是为一个对象提供一个或用品或者占位符,以便于控制对它的访问
- 在生活当中我们可以找到很多代理模式的场景。比如:如果你想请一个明星,来办一场商业演出。只能联系他的经纪人,经纪人呢,会把商业演出的这些细节和报酬都谈好之后,再把合同交给这个明星去签合同。
- 代理模式的关键是当客户不方便直接访问一个对象,或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问。客户实际上访问的是提升对象替身对象对这个请求做出一些处理之后再把请求转交给这个本体对象。
客户---->本体(不用代理)
客户---->代理---->本体(使用代理模式)
3.1小明追MM的故事
- 在2020年3月14日的清晨,小明遇见了他的百分百女孩,暂且称呼小明的女神为A,两天之后,小明决定给送一束花来表白。刚好呢,小明打听到A和他有一个共同的朋友B,于是小明决定让B来代替自己完成送花这件事情。
虽然小明的故事必然以悲剧收场,因为这妹子更好的方式是一辆宝马,或者一把钥匙。
var Flower=function(){}
// 不使用代理模式
// 送花
var xiaoming={
sendFlower:function(){
var flower=new Flower();
target.reciveFlower(flower);
}
}
// 小明的女神A
var A={
reciveFlower:function(flower){
console.log('收到花'+Flower)
}
}
// 代理B
var B={
reciveFlower:function(){
A.reciveFlower(flower);
}
}
xiaoming.sendFlower(B);
- 现在我们改变故事的背景设定。假设当A接在心情好的时候收到的花啊,小明表白的成功几率呢就会有60%。假设在心情不好的时候收到花,小明的成功几率,无限接近于零。
- 小明跟A刚刚认识两天。他没有能力来去辨别A什么时候心情好,什么时候心情不好。小明如果直接把花送给A,花被直接扔到垃圾桶的可能性会很大的。对吧,成功几率也就很小了。你要想像小俊这样的人,有可能这束花是吃了一个月泡面省下来的对吧?这个很操心呐,啊要命的事儿。
- 但是事情有转机。朋友B很了解A,所以小明只管把花交给B,B会去监听A的心情变化,在适当的时候,把花送给A。那这样的话成功的几率岂不是就很大啦。
var Flower=function(){}
// 小俊送花
var xiaojun={
sendFlower:function(target){
var flower=new Flower();
target.reciveFlower(flower);
}
}
// 代理B
var B={
reciveFlower:function(flower){
A.listenGoodMood(function(){//监听A的好心情
A.reciveFlower(flower);
})
}
}
// 小俊的女神A
var A={
reciveFlower:function(flower){
// 十秒钟之后诶的心情变好。
setTimeout(function(){
fn();
},10000)
}
}
3.2 什么叫保护代理和虚拟代理
- 代理B可以帮助A过滤掉一些请求。比如,送花的人当中呢,形形色色的都有。那A的要求是要有长得帅的,并且还要有宝马的。那如果在这个过程当中,送花的人年龄太大,长得不帅。还没有宝马的这种请求,就可以直接在代理B这个层面被拒绝掉。这种代理呢叫做保护代理。实际上呢,A和B充当了两个角色:一个白脸,一个黑脸。白脸A,继续保持他的女神形象,不希望直接拒绝任何人,于是找到了黑脸B来去控制有人对A的访问。
- 在现实生活当中,花价格不菲,导致在程序世界里面,我们在写代码的时候,想要找一个花儿,
- 也只是new flower()对象,实际上代价昂贵!因为每送一次花都要new Flower(),
- 我们可以把事情交给代理B去做。代理B呢,会选择在心情好的时候,去执行,new flower的操作,而不是每一次都由我们自己来去做开销很大的操作;这是代理模式当中的另外一种形式。叫做虚拟代理。虚拟代理呢,把一些开销量很大的对象动作,延迟到真正需要他的时候才去执行,才去创建。
var B={
reciveFlower:function(flower){
A.listenGoodMood(function(){
var flower=new Flower();
A.reciveFlower(flower);
})
}
}
3.3虚拟代理现实图片预加载
- 在web开发当中,图片预加载是最常用的一种技术,如果直接给某一个img标签节点设置src属性。我们要考虑到图片的大小,或者说图片过大或者网络不佳,对于我们造成的一些影响,比如说图片的位置往往有一段时间的是空白的,最常见的这个做法呢,就是先用一张loading图占位,然后用异步的方式去加载这些图片。等到图片加载好了之后再把它填充在img节点里边儿去,这种场景呢就适合我们使用这个虚拟代理来实现。
var myImage=(function(){
var imgNode=document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc:function(src){
imgNode.src=src;
}
}
})();
myImage.setSrc('');
// 引入代理对象。ProxyImage。我们通过这个代理对象,在图片被真正加载好之前,页面中将会出现一张占位符占位的图片。来提示用户呢,图片正在加载当中。
var myImage = (function () {
var div = document.createElement('div');
div.className = 'box';
var imgNode = document.createElement('img');
div.appendChild(imgNode);
document.body.appendChild(div);
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('imgs/loading.jpeg');
img.src=src;
}
}
})()
proxyImage.setSrc('imgs/4.jpeg');
// 常见的处理方式:
var myImage=(function(){
var imgNode=document.createElement('img');
document.body.appendChild(imgNode);
var img=new Image;
img.onload=function(){
imgNode.src=img.src;
}
return {
setSrc:function(src){
imgNode.src='loading.jpeg';
img.src=src;
}
}
})()