0%

策略模式指的是定义一系列算法,把他们一个个封装起来,目的就是将算法的使用和算法的实现分离开来。stragey就是值为函数的变量,同时它还可以用来封装一系列的规则,比如常见的表单验证规则,只要这些规则指向的目标一致,并且可以被替换使用,那么就可以用策略模式来封装它们。

优点

  • 算法可以自由切换,避免了使用多层条件判断,增加了扩展性

缺点

  • 策略类增多,所有策略类都需要对外暴露。

例子

  • 写表单验证经常无止境的if…else写法,意识到这种写法不靠谱,于是我把检验规则放在一个对象中,在函数中对它进行控制,把规则与实现进行了分离,每次只需要在封装的规则中去修改配置。在后面的多种场景都用这种方法,解决了频繁使用if…else的问题,当第一次接触倒策略模式才知道这种写法也算策略模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const rules = {
cover_img: {
must: false,
msg: '请上传封面图片',
val: ''
},
name: {
must: true,
msg: '姓名不能为空',
val: ''
},
sex: {
must: true,
msg: '请填写性别',
val: ''
},
birthday: {
must: false,
msg: '请选择生日',
val: ''
},
}
function verify(){
for(const key in rules){
if(rules[key].must&&!rules[key].val){
console.log(rules[key].msg)
}
}
}
verify()

对于分支语句的优化,工厂方法模式,状态模式,策略模式。

表单验证例子:高阶函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<form action="http://xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName"></input>
输入密码:<input type="text" name="password">
输入手机号码:<input type="text" name="phoneNumber">
<button>提交</button>
</form>
<script>
//策略对象
let 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
}
}
}
//客户调用代码
let registerForm = documetn.getElementById('registerForm')
let validatorFunc = function(){
let validator = new Validator()
validator.add(registerForm.userName,[{
strategy:'isNonEmpty',
errorMsg:'用户名不为空'
},{
strategy:'minLength:6',
errorMsg:'用户名长度不能小于10位'
}]);
validator.add(registerForm.password,[{
strategy:'minLength:6',
errorMsg:'用户长度不能小于10位'
}])
validator.add(registerForm.phoneNumber,[{
strategy:'isMobile',
errorMsg:'手机号码格式不正确'
}])
}
//验证类
Validator = function(){
this.cache=[];
}
Validator.prototype.add = function(dom,rules){
var self = this
for(let i=0,rule;rule = rules[i++];){
let self = this
(function(rule){
let strategyAry = rule.strategy.split(':')
let errorMsg = rule.errorMsg
self.cache.push(function(){
let strategy = strategyAry.shift()
strategyAry.unshift(dom.value)
strategyAry.push(errorMsg)
return strategies[strategy].apply(dom,strategyAry)
})
})(rule)
}
}
Validator.prototype.start = function(){
for(let i=0,validatorFunc;validatorFunc = this.caches[i++];){
let errorMsg = validatorFunc()
if(errorMsg){
return errorMsg
}
}
}
</script>

工厂方法模式:是一种创建型模式,最终目的是创建对象

状态模式和策略模式都是行为性模式,在状态模式中,核心是对状态的控制来决定表现行为,所以状态之间通常不能相互替代,否则将产生不同的行为结果。

模板方法的核心在于对方法的重用,它将核心方法封装在抽象类中,让子类继承抽象类的方法,实现抽象类方法的共享,达到方法的共用,当然这种设计模式将导致抽象类控制子类必须遵守某些法则,这是一种行为的约束,但是为了让行为的约束更加可靠,抽象类中封装的方法通常是不变的算法,或者具有稳定的调用方式。

继承实现模板方法模式

子类继承的方法也可以扩展,要求对抽象类继承的方法进行重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//模板类 基础提示框data渲染数据
var Alert=function(data){
if(!data)return;
//设置内容
this.content=data.content;
//创建提示面板
this.panel=document.createElement('div');
//创建内容提示组件
this.contentNode=document.createElement('p');
//创建确定按钮组件
this.confirmBtn=document.createElement('span');
//创建关闭按钮组件
this.closeBtn=document.createElement('b');
//为提示面板添加类
this.panel.className='alert';
//为关闭按钮添加类
this.closeBtn.className='a-close';
//为确定按钮添加类
this.confirmBtn.className='a-confirm';
//为确定按钮添加文案
this.confirmBtn.innerHTML=data.confirm||'确认'
//为提示内容添加文本
this.contentNode.innerHTML=this.content;
//点击确定按钮执行方法 如果data中有success方法则为success方法,否则为空函数
this.success=data.success||function(){};
//点击关闭按钮执行方法
this.fail=data.fail||function(){}
}
Alert.prototype={
init:function(){
//生成提示框
this.panel.appendChild(this.closeBtn);
this.panel.appendChild(this.contentNode);
this.panel.appendChild(this.confirmBtn);
//插入页面
document.body.appendChild(this.panel);
//绑定事件
this.bindEvent();
//显示提示框
this.show();
},
bindEvent:function(){
var me=this;
//关闭按钮点击事件
this.closeBtn.onclick=function(){
me.fail();
//隐藏弹层
me.hide()
}
//确认按钮点击事件
this.confirmBtn.onclick=function(){
me.success();
me.hide();
}

},
hide:function(){
this.panel.style.display='none'
},
//显示弹层
show:function(){
this.panel.style.display='block'
}

}
//右侧按钮提示框
var RightAlert=function(data){
//继承基本提示框构造函数
Alert.call(this,data);
//为确认按钮添加right类
this.confirmBtn.className=this.confirmBtn.className+'right'
}
//继承基本提示框方法
RightAlert.prototype=new Alert();
//标题提示框
var TitleAlert=function(data){
Alert.call(this,data);
this.title=data.title;
this.titleNode=document.createElement('h3');
this.titleNode.innerHTML=this.title
}
TitleAlert.prototype=new Alert();
//对基本提示框创建方法的拓展
TitleAlert.prototype.init=function(){
//插入标题
this.panel.insertBefore(this.titleNode,this.panel.firstChild);
//继承基本提示框的init方法
Alert.prototype.init.call(this)
}
//带有取消按钮的弹出框
var CancleAlert=function(data){
//继承标题提示框构造函数
TitleAlert.call(this,data)
//取消按钮文案
this.cancel=data.cancel;
//创建取消按钮
this.cancelBtn=document.createElement('span');
//为取消按钮添加类
this.cancelBtn.className='cancel'
//设置取消按钮内容
this.cancelBtn.innerHTML=this.cancel||'取消'

}
//继承标题提示框原型方法
CancleAlert.prototype=new Alert()
CancleAlert.prototype.init=function(){
//继承标题提示框创建方法
TitleAlert.prototype.init.call(this);
//由于取消按钮要添加在末尾,所以在创建完其他组件后添加
this.panel.appendChild(this.cancelBtn)
}
CancleAlert.prototype.bindEvent=function(){
var me=this;
//标题提示框绑定事件方法继承
TitleAlert.prototype.bindEvent.call(me);
//取消按钮绑定事件
this.cancelBtn.onclick=function(){
me.fail();
me.hide();
}
}
new CancleAlert({
title:'提示标题',
content:'提示内容',
success:function(){
console.log('ok')
},
fail:function(){
console.log('cancel')
}
}).init()

JavaScript没有抽象类的缺点和解决方案

缺点:

  • Js并没有从语法层面提供对抽象类的支持,抽象类的第一个作用是隐藏对象具体类型,由于Js是一门“类型模糊”的语言,所以隐藏对象的类型在Js中并不重要
  • 另一方面,当我们在Js中使用原型继承模拟传统的类式继承,并没有编译器帮助我们进行任何形式的检查,我们也没有办法保证子类会重写父类的“抽象方法”,在Java中编译器会保证子类重写父类中的抽象方法,但是Js并没有进行这些检查工作,当我们在编写代码时得不到任何形式的警告,完全寄托于程序员的记忆力是很危险的,特别是当我们使用模板方法模式这种完全依赖继承而实现的设计模式时。

解决方案:

  • 使用鸭子类型模拟接口检查,确保子类中确实重写了父类的方法,但模拟接口检查会带来不必要的复杂性,而且要求程序员主动进行接口检查,就要求在业务代码中添加一些和业务逻辑无关的代码
  • 让Beverage.prototype.brew等方法直接抛出一个异常,如果因为粗心忘记编写Coffee.prototype.brew方法,那么至少我们会在程序运行时得到一个错误
1
2
3
4
5
6
Beverage.prototype.brew = function(){
throw new Error('子类必须重写父类的brew方法')
}
Beverage.prototype.pourInCup = function(){
throw new Error('子类必须重写父类的pourInCup方法')
}

第二种方法实现简单,付出的额外代价小,缺点是我们得到错误信息的时间点太靠后

我们一共有3中机会可以得到这个错误信息,第一次是在编写代码的时候,通过编译器检查来得到错误信息,第二次是在创建对象的时候用鸭子类型进行“接口检查”,最后一次是在程序运行过程中

用高阶函数实现模板继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let Beverage = function(param){
let boilWater = function(){
console.log("把水煮沸")
}
let brew = param.brew || function(){
throw new Error('必须传递brew方法')
}
let pourInCup = param.pourInCup || function(){
throw new Error('必须传递pourInCup方法')
}
let addCondiments = param.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 coffee = new Coffee()
coffee.init()

当我们把brew,pourInCup,addCondiments这些方法一次传入Beverage函数,Beverage函数被调用之后返回构造器F,F类中包含了“模板方法” F.prototype.init,跟继承得到的效果一样,该“模板方法依旧封装了子类的算法框架

组合模式能够给我们提供一个清晰的组成结构,组合对象类通过集成同一个父类使其具有统一的方法,方便我们统一管理和使用。

组合模式用途:

  • 组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构,除了用来表示树形结构外,组合模式可以通过对象的多态性表示,使得用户对单个对象和组合对象的使用具有一致性
  • 请求在树中传递,从树最顶端往下传,如果当前处理请求的对象是叶对象(普通子命令),叶对象会对请求做出相应处理,如果当前处理请求是组合对象,组合对象则会遍历它属下的子节点,将请求继续传递给子节点。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<body>
<button id="button">按我</button>
<script>
let MacroCammond = function(){
return{
commandList:[],
add: function(command){
this.commandList.push(command)
},
execute: function(){
for(let i = 0,command;command = this.commandList[i++];){
command.execute()
}
}
}
};
//
let openAcCommand = {
execute: function(){
console.log('打开空调')
}
}
let openTvCommand = {
execute: function(){
console.log('打开电视')
}
}
let macroCommand1 = MacroCammond()
macroCommand1.add(openAcCommand)
macroCommand1.add(openTvCommand)
//
let closeDoorCommand = {
execute:function(){
console.log('关门')
}
}
let openComputer = {
execute: function(){
console.log('开电脑')
}
}
let macroCommand2 = MacroCammond()
macroCommand2.add(closeDoorCommand)
macroCommand2.add(openComputer)
//现在所有命令组合在一起为一个超级命令
let macroCommand = MacroCammond()
macroCommand.add(macroCommand1)
macroCommand.add(macroCommand2)
//绑定超级命令
let setCommand = (function(command){
document.getElementById('button').onclick = function(){
command.execute()
}
})(macroCommand)


</script>

</body>

注意:

  1. 组合模式不是父子关系

组合模式是一种HAS-A(聚合)关系,而不是IS-A,组合对象包含一组叶对象,但是leaf不是Composite的子类,组合对象把请求委托给它包含的叶子结点,它们能合作的关键是拥有相同的接口。

  1. 对叶子操作的一致性:

组合模式除了要求组合对象和叶对象拥有相同的接口外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性

  1. 双向映射性:

给父结点和子结点建立双向映射关系,例如给父对象和叶子分别添加集合保存对方的引用,但是,相互之间的引用1过于复杂时会引使得对象之间产生过多耦合性,可以引入中介者模式管理对象

组合模式适用情况:

  • 表示对象的部分-整体层次结构,组合模式可以很方便地构造一棵树来表示对象的部分-整体结构,特别是开发期间不确定这棵树存在多少层次的时候,在树的构造最终完成之后,只需要通过请求树的最顶层对象,就能对树做统一的操作。在组合模式中增加和删除结点很方便,符合“开放-封闭”原则
  • 客户希望统一对待树中所有对象。组合模式可以使客户忽略对象和叶对象的区别,客户在面对这棵树时,不用关心正在处理的对象时组合对象还是叶对象,也就是不用写一堆if,else组合对象和叶对象会各自做正确的事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var ListPartition=(head,pivot)=>{
let sH=null,sT=null,eH=null,eT=null,mH=null,mT=null;
let next=null;
while(head!==null){
next=head.next;
head.next=null;
if(head.value<pivot){
if(sH===null){
sH=head;
sT=head;
}else{
sT.next=head;
sT=head;
}
}else if(head.value===pivot){
if(eH===null){
eH=head;
eT=head;
}else{
eT.next=head;
eT=head;
}
}else{
if(mH==null){
mH=head;
mT=head;
}else{
mT.next=head;
mT=head;
}
}
head=next;
}
if(sT!==null){//如果有小于区域
sT.next=eH;
eT=eT===null?sT:eT;//下一步谁连大于区域的头谁就是eT

}
if(eT!==null){//小于区域和等于区域不是都没有
eT.next=mH;
}
return sH!==null?sH:(eH!==null?eH:mH)

}

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

提取共同点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//抽象处公共部分
function changeColor(dom,color,bg){
//设置元素的字体颜色
dom.style.color=color
//设置元素的背景颜色
dom.style.background=bg
}
var spans=document.getElementsByTagName('span')
spans[0].onmouseover=function(){//匿名函数作为回调函数作为桥接方法,解除this和事件之间的耦合,changeColor方法中的dom实质上是事件回调函数中的this
changeColor(this,'red','#ddd')
}
spans[0].onmouseout=function(){
changeColor(this,'#333','#f5f5f5')
}

桥接模式最主要特点就是将实现层(如元素绑定的事件)与抽象层(如修饰页面UI逻辑)解耦分离,使两部分可以独立变化,桥接模式主要是针对结构之间的结构,而抽象工厂模式和创建者模式主要业务在于创建。通过桥接模式实现的解耦,使实现层和抽象层分开处理,避免需求的改变造成对象内部的修改。

装饰器模式是在对原有功能的基础上对功能拓展的模式,能够在不改变对象自身的基础上,在程序运行期间给对象动态增添职责

为输入框增加新功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//装饰者
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('tel_input',function(){
document.getElementById('tel_demo_text').style.display='none'
})
//姓名输入框功能装饰
decorator('name_input',function(){
document.getElementById('name_demo_text').style.display='none'
})

装饰链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let plane ={
fire:function(){
console.log('发射普通子弹')
}
}
let missileDecorator = function () {
console.log('发射导弹')
}
let atomDecorator = function(){
console.log('发射原子弹')
}
let fire1 = plane.fire
plane.fire=function(){
fire1()
missileDecorator()
}
let fire2 = plane.fire
plane.fire = function(){
fire2()
atomDecorator()
}

plane.fire()//发射普通子弹 发射导弹 射原子弹

AOP装饰函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.prototype.before = function(beforefn){
let _self = this//保存原函数的引用
return function(){//返回包含了原函数和新函数的代理函数
beforefn.apply(this,arguments)//执行新函数,且保证this不被劫持,新函数接受的参数也会被原封不动地传入原函数,新函数在原函数之前执行
return _self.apply(this,arguments)//执行原函数并返回原函数的执行结果,并且保证this不被劫持
}
}
Function.prototype.after = function(afterfn){
let _self = this
return function(){
let ret = _self.apply(this,arguments)
afterfn.apply(this,arguments)
return ret
}
}
document.getElementById= document.getElementById.before(function(){
alert(1)
})
let button = document.getElementById('button')

应用:

数据统计上报:

把行为依照职责分成细粒度更细的函数,通过装饰把它们合并到一起,有助于编写一个松耦合高复用得到系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.after = function(afterfn){
let _self = this
return function(){
let ret = _self.apply(this,arguments)
afterfn.apply(this,arguments)
return ret
}
}
let showLogin = function(){
console.log('打开登录浮层')
}
let log = function(){
console.log('上报标签为:'+this.getAttribute('tag'))
}
showLogin = showLogin.after(log)//打开浮层后上报数据
document.getElementById('button').onclick = showLogin

动态改变参数

1
2
3
4
5
6
7
8
9
10
11
//动态改变参数
let ajax = function(type,url,param){
console.log(param)
}
let getToken = function(){
return "Token"
}
ajax = ajax.before(function(type,url,param){
param.Token = getToken()
})
ajax('get','http://xxx.com',{name:'seven'})

用AOP的方式给ajax函数动态装饰上Token参数,保证了ajax函数是一个相对纯净的函数,提高了ajax函数的复用性。

表单验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//表单验证
let validate = function(){
if(username.value === ''){
alert('用户名不能为空')
return false
}
if(password.value === ''){
alert('密码不为空')
return false
}
}
Function.prototype.before = function(beforefn){
let _self = this
return function(){
if(beforefn.apply(this,arguments)===false){
return ;
}
return _self.apply(this,arguments)
}
}
let formSubmit = function(){
let param ={
username:username.value,
password:password.value
}
ajax('http://xxx.com/login',param)
}
formSubmit=formSubmit.before(validate)
let submit = document.getElementById('submitBtn')
submit.onclick=function () {formSubmit()}

代理模式和装饰者模式区别:

目的不同:代理模式的目的是当直接访问本体不方便或者不符合需要的时候,为这个本体提供一个替代者,本体定义关键功能,代理提供或拒绝对它的访问,这种关系一开始就可以被确定。装饰者模式用于一开始无法确定对象的全部功能。

形式:代理模式只有一层代理-本体的引用,而装饰者模式是会形成一条长长的装饰链

JQuey适配器

如果A框架和jQuery很像我,直接用适配器适配两种代码库中不兼容的代码,window.A=A=jQuery

参数适配器

当传递多个参数,记住参数顺序比较困难,所有我们经常以一个参数对象方式传入,调用它的时候又不知道传递的参数是否完整,如果有一些参数没有传入,一些参数是有默认值的等等,此时用适配器传入这个参数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function doSomething(obj){
var _adapter={
name:'lala',
title:'设计模式',
age:24,
color:'pink',
size:100,
prize:50
}
for(var i in _adapter){
_adapter[i]=obj[i]||_adapter[i]
}

}

数据适配

var arr=[‘JavaScript’,’book’,’前端编程’]

这种数据结构语义不好,通常会适配成对象形式,比如:

1
2
3
4
5
6
7
8
9
10
function arrToObjAdapter(arr){
return {
name:arr[0],
type:arr[1],
title:arr[2]

}
}
var adapterData=arrToObjAdapter(arr)
console.log(adapterData)

服务器端数据适配

如果后端因为架构改变导致传递的数据结构发生变化,我们只需要写个适配器将传递过来的数据适配成对我们可用的数据再使用

1
2
3
4
5
6
7
8
9
10
11
12
13
function ajaxAdapter(data){
//处理数据并返回数据
return [data['key1'],data['key2'],data['key3']]
}
$.ajax({
url:'someAddress.php',
success:function(data,status){
if(data){
//使用适配器后的数据——返回的对象
doSomething(ajaxAdapter(data))
}
}
})

外观模式:为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口的访问更容易,在javaScript中用于对底层结构兼容性做统一封装来简化用户使用

兼容性优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function addEvent(dom,type,fn){
//支持DOM2的事件处理程序addEventListener方法的浏览器
if(dom.addEventListener){
dom.addEventListener(type,fn,false);
//对于addEventListener方法但支持attachEvent方法的浏览器
}else if(dom.attachEvent){
dom.attachEvent('on'+type,fn)
//对于不支持addEventListener方法也不支持attachEvent方法,但支持'on+事件名'的浏览器
}else{
dom['on'+type]=fn
}
}
//获取事件对象
var getEvent=function(event){
return event||window.event
}
//获取元素
var getTarget=function(event){
var event=getEvent(event)
//标准浏览器下event.target,IE下event.srcElement
return event.target||event.srcElement

}
//阻止默认行为
var preventDefault=function(event){
var event=getEvent(event)
//标准浏览器
if(event.preventDefault){
event.preventDefault()
//IE浏览器
}else{
event.returnValue=false;
}
};

addEvent(myInput,'click',function(e){
preventDefault(e)
//获取事件源目标对象
if(getTarget(e)!==document.getElementById('myInput')){
hideInputSug()
}
})

对接口方法的外层包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var A={
//通过id获取元素
g:function(id){
return document.getElementById(id)
},
//设置元素css属性
css:function(id,key,value){
document.getElementById(id).style[key]=value
},
//设置元素的属性
attr:function(id,key,value){
document.getElementById(id)[key]=value
},
html:function(){
document.getElementById(id).innerHTML=html
},
//为元素绑定事件
on:function(){
document.getElementById(id)['on'+type]=fn
}
}
A.css('box','background','red')//设置css样式
A.attr('box','className','box')//设置class
A.html('box','这是新添加的内容')//设置内容

创建代码库

单例模式是一个只允许实例化一次的对象类,有时这么做是为了节省系统资源,javaScript中单例模式经常会作为命名空间对象来实现,通过单例对象可以将各个模块的代码井井有条的梳理在一起,减少使用全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var A={
Util:{
util_method1:function(){},
util_method2:function(){}
//...
},
Tool:{
tool_method1:function(){},
tool_method2:function(){}
//...
},
Ajax:{
get:function(){},
post:function(){}
//...
},
others:{
//...
}

}
A.Util.util_method1()
A.Tool.tool_method1()
A.Ajax.get()

单例模式管理静态变量

静态变量:只能访问不能修改并且创建后就能使用,为了实现创建后就能使用这一需求,我们需要让创建的函数执行一次,我们创建的对象内保存着静态变量通过取值器访问,最后将这个对象作为一个单例放在全局空间里作为静态变量单例对象供他人使用,闭包封装私有变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var Conf=(function(){
var conf={
MAX_NUM:100,
MIN_NUM:1,
COUNT:1000
}
//返回取值器对象
return {
get: function(name){
return conf[name]?conf[name]:null;
}
}
})()
var count=Conf.get('COUNT')
console.log(count)//1000

惰性单例

在需要的时候才创建对象实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//惰性单例
let getSingle=function(fn){
let res;
return function(){
return res || (res = fn.apply(this,arguments))
}
}
let createLoginLayer = function(){
let div = document.getElementById('div')
div.innerHTML = "我是登录浮窗"
div.style.display='none'
document.body.appendChild(div)
return div
}
let createSingleLoginLayer = getSingle(createLoginLayer)
document.getElementById('loginBtn').onclick = function(){
let loginLayer = createSingleLoginLayer()
loginLayer.style.display='block'
}

res在闭包中,因此永远不会被销毁,在将来的请求中如果res被赋值就直接返回这个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//图片轮播类
var LoopImages=function(ImgArr,container){
this.ImgArr=ImgArr;//轮播图片数组
this.container=container//轮播图片容器
}
LoopImages.prototype={
//创建轮播图片
createImg:function(){
console.log('LoopImages createImage function')

},
changeImage:function(){
console.log('LoopImages changeImage function')
}
}
//上下切换滑动类
var SlideLoopImg=function(imgArr,container){
//构造函数继承图片轮播类
LoopImages.call(this,imgArr,container)
}
SlideLoopImg.prototype=new LoopImages()
//重写继承的切换下一张图片的方法
SlideLoopImg.prototype.changeImage=function(){
console.log('SlideLoopImage changeImage function')
}
//渐隐切换类
var FadeLoopImg=function(imgArr,container,arrow){
LoopImages.call(this,imgArr,container)
this.arrow=arrow
}
FadeLoopImg.prototype=new LoopImages()
FadeLoopImg.prototype.changeImage=function () {
console.log('FadeLoopImage changeImage function')
}

父类将简单的属性放在构造函数中,将复杂的方法放在原型中,子类通过组合继承或者寄生组合继承将父类的方法和属性继承,子类可以将方法重写

需要让每个继承对象独立拥有一份原型对象,或者创建的实例对象的构造函数比较复杂,或者耗时较长,或者通过创建多个对象实现,此时我们最好不用new关键字去复制这些基类,可以通过对这些对象属性和方法进行复制来实现创建,如果涉及多个对象,我们也可以通过原型模式来实现对新对象的创建。首先要有一个原型模式的对象复制方法

基于已经存在的模板对象克隆新对象的模式

argument[0],arguments[1]…参数1,参数2…表示模板对象

这里对模板引用是浅复制,也可以根据需求深复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function prototypeExtend(){
var F=function () { },//缓存类,为实例化返回对象临时创建
args=arguments,//模板对象参数序列
i=0,
len=args.length;
for(;i<len;i++){
//遍历每个模板对象中的属性
for(var j in args[i]){
//将这些属性复制到缓存类原型中
F.prototype[j]=args[i][j]
}
}
//返回缓存类的一个实例
return new F()
}
var penguin=prototypeExtend(
{
speed:20,
swim:function(){
console.log('游泳速度'+this.speed)
}
},
{
run:function(speed){
console.log('跑步速度'+speed)
}
},
{
jump:function(){
console.log('跳跃动作')
}

}

)
penguin.swim();//游泳速度 20
penguin.run(10)//奔跑速度 10
penguin.jump()//跳跃动作