JS之设计模式
## 软件开发设计流程
需求----分析----设计-----编码----测试----部署----运维
## 开发方式
### 瀑布式(假设需求没有变化,不会重复)
需求----分析----设计-----编码----测试----部署----运维
### 迭代式(敏捷)
没有需求或需求完全不明确
不断迭代下面的阶段,需求在不断变化
需求----分析----设计-----编码----测试
导致代码量不断增长----重复的代码-----代码很乱---效率不高-----重构
### 重构
采用设计模式方式重构代码,将冗余的代码精简化
### 做项目,做产品
项目------只需要完成功能
产品------代码很规范,尽量做到最好,最优
## 什么是设计模式?
设计模式---软件开发过程中总结出来最好的代码范式(需要特定场景)
问题-----红烧鱼块?
解决办法:----妈妈说了步骤----比较冗余
----百度搜索制作步骤----精练----最简最优
23种设计模式---只需知道常用
单例------
问题----只需要有一个实例
解决办法:-----根据经验写---比较冗余
-----根据单例范式写----最简最优
设计模式---抽象---不好理解
工厂模式
//工厂模式
//问题:需要根据配置文件自动实例化很多对象
//解决:采用工厂模式
function personFactory(name, age, sex) {//定义工厂factory,用来创建对象
var obj = new Object(); //定义一个空对象
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.showName = function(){//给对象添加方法
return this.name;
}
return obj; //返回对象
}
//工厂生成对象
var p1 = personFactory("张三","22","男");
var p2 = personFactory("李四","23","男");
console.log(p1.showName())
console.log(p1.constructor)//输出类型---object
//缺点:js里面不清楚p1,p2的构造函数,即不清楚是由哪个函数构造
//构造函数模式
//问题:需要根据配置文件自动实例化很多对象, 如系统启动需要实例很多对象
//解决:采用工厂模式
function PersonFactory(name, age, sex) {
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.showName = function(){
return this.name;
}
// return this;
}
var p = new Person(name, age, sex);
return p;
}
var p1 = PersonFactory("张三","22","男");
var p2 = PersonFactory("李四","23","男");
console.log(p1.showName())
console.log(p1.constructor) //默认指向自己的构造函数Person
//优点:可以知道实例对象的构造函数
单例模式
//单例(单一的实例)
//在系统中只能有且只有一个实例, 如资源管理器
//前端如何使用? 如页面上需要多个弹框,需要限制只能有一个(应用场景)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单例应用</title>
</head>
<body>
<button id="btn">打开弹框</button>
<script>
//创建一个div
// var createWindow = function(){
// var div = document.createElement("div");//在内存中生成一个div
// div.innerHTML = "我是弹框,弹框应该唯一"; //给div加内容
// div.style.display = "none"; //默认隐藏
// document.body.appendChild(div);//把div写入页面
// return div;
// }
// btn.onclick = function(){
// var win = createWindow(); //创建弹框
// win.style.display = "block"; //显示弹框
// }
//缺点:每一次createWindow()都会在内存中生成一个div副本,没有必要
//可以使用单例思想重构
//使用单例模式重构
var createWindow = (function(){
var div;
return function(){ //闭包===函数+外部局部变量(缓存)
if (!div) { //若div不存在则创建,否则直接返回
div = document.createElement("div");//在内存中生成一个div
div.innerHTML = "我是弹框,弹框应该唯一"; //给div加内容
div.style.display = "none"; //默认隐藏
document.body.appendChild(div);//把div写入页面
}
return div;
}
})(); //()表示执行
btn.onclick = function(){
var win = createWindow(); //创建弹框 //等价于29的function
win.style.display = "block"; //显示弹框
}
</script>
</body>
</html>
观察者模式
//观察者---发布订阅
//场景----买饭(跟服务员说----买XXX饭----服务员下单(订阅)-----服务员通知(发布)-----)
//观察者----服务员
//被观察者-----订单
var releaseObj = {}; //发布者 观察者
releaseObj.list = []; //订单列表
//添加订单行为
releaseObj.listen = function(fn) { //fn----订单
releaseObj.list.push(fn);//把订单存入订单列表
}
//客户通知服务员下单----添加到list列表里面
releaseObj.listen(function(name, price){ //订单
//若时间太长,取消订单(直接把list中的函数去掉)
console.log("红烧肉饭订单:name:", name, ",price:", price, this.list);
})
//等待-----玩手机-----等待服务员的通知
releaseObj.notify = function(){ //通知订单ok
for(var i=0, fn; fn=this.list[i++];) {//var i=0; i<xxx; i++
//arguments---会接收所有实参
//执行函数 call----参数,参数 apply---[参数,参数]
//fn.apply---表示执行fn
//fn(this, arguments) fn---function(name, price)
//目的:执行fn 保证fn在this作用域里,将所有实参传入fn
fn.apply(this, arguments); //this---releaseObj arguments---参数
// fn.call(this, "红烧肉饭", "20");
}
}
releaseObj.notify("红烧肉饭", "20"); //通知饭已Ok
模板模式
模板----
模具----已经存的一种框架,实物,电子档
饼干----模具
定义一个抽象的对象(bike),有一个固定的方法(如滚动)
----根据这个抽象对象去实现子类(捷安特)
----也须实现抽象方法(如滚动)
//模板模式----用于构建统一的对象----所有对象拥有统一的行为
//定义抽象自行车----不存在----约定
//老曹----继承财产-----把国籍转为中国
//约定----要创建一子类bike实现必须实现wheel方法和hdrive方法
var Bike = function(){ //模板对象
} //空对象
Bike.prototype.wheel = function(){ //空对象定义轮子
throw new Error("抽象轮子方法");
}
Bike.prototype.hdrive = function(){ //空对象定义动力系统
throw new Error("抽象动力系统");//避免子类不实现该方法,直接调用报错
}
// 不能这样写 因为调方法就直接报错
// var bike = new Bike();
// bike.hdrive()
//电动车模板
var EBike = function(){}
EBike.prototype.wheel = function(){ //空对象定义轮子
throw new Error("抽象轮子方法");
}
EBike.prototype.edrive = function(){ //空对象定义动力系统
throw new Error("抽象动力系统");//避免子类不实现该方法,直接调用报错
}
//根据模板 造子类捷安特
var JATBike = function(){
Bike.call(this); //把bike的属性拷贝过来
}
JATBike.prototype = new Bike();
JATBike.prototype.wheel = function(){
//业务逻辑
console.log("捷安特轮子");
}
JATBike.prototype.hdrive = function(){
//业务逻辑
console.log("人力驱动");
}
var jatBike = new JATBike();
jatBike.hdrive();
//根据电动车模板---造子类雅迪
var YdBike = function(){
EBike.call(this);
}
YdBike.prototype = new EBike();
YdBike.prototype.wheel = function(){
//业务逻辑
console.log("捷安特轮子");
}
YdBike.prototype.edrive = function(){
//业务逻辑
console.log("电力驱动");
}
适配器模式
适配器---
手机充电---需要2孔
电脑充电---需要3孔----买插座----墙上2孔
插座---2孔----3孔
问题:
有了自行车,电动车----自行车太累了,电动车买不起-----改装自行车用二手电动车马达
/adapter----适配器-----源码
//改装自行车
var BikeAdapter = function(engine){
Bike.call(this);
this.engine = engine; //马达
}
BikeAdapter.prototype = new Bike();
BikeAdapter.prototype.wheel = function(){
//业务逻辑
console.log("捷安特轮子");
}
BikeAdapter.prototype.hdrive = function(){
//业务逻辑
console.log("马达驱动", this.engine.edrive()); //不再需要人力驱动
}
var ydBike = new YdBike();
var bikeAdapter = new BikeAdapter(ydBike);
bikeAdapter.hdrive(); //改装后的自行车
代理模式
代理----设计模式
别人代理你的行为,如明星---经理人----行为必须一致,经理人行事必须是按明星要求来
webpack--proxy(代理)---将请求--直接过不去--需要代理转发
//代理---代理对象要与被代理对象有相同行为
//男----送礼物-----委托代理送
var girl = function(name) {
this.name = name;
}
var boy = function(girl) {
this.girl = girl;
//送礼物
this.sendGift = function(gift) {
//业务逻辑
console.log("hi "+girl.name+ ", 有一个男孩送你礼物:"+gift);
}
}
//代理对象---具备了送礼物能力
var proxyObj = function(girl) {
this.girl = girl;
//送礼物
this.sendGift2 = function(gift) {
//new实例化boy对象
//var boy = new boy(this.girl);
//boy.sendGift(gift);
(new boy(this.girl)).sendGift(gift); //送礼物 用了男孩子的方法
}
}
//调用过程----只知道是代理对象所为
var proxy = new proxyObj(new girl("小花"));
proxy.sendGift2("99朵玫瑰");
例子:前端加载图片(真实图片–很大, 等待图片—很小—代理)
请看proxy.html (见下)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代理应用</title>
</head>
<body>
<script>
//创建一个图片对象-----冗余----耦合度高
/*var myImage = (function(){
//imgNode---是用来显示图片的
var imgNode = document.createElement("img"); //创建一个图片
document.body.appendChild(imgNode); //图片添加到body
//img ---- 不用来显示,只用来加载, 加载完成就替换
var img = new Image();
img.onload = function(){//当真实图片下载完成时触发
setTimeout(()=>{
imgNode.src = this.src;//用真实图片替换临时图片 this----img
}, 1000);
}
return {
setSrc: function(src) {
imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif"; //临时图片
img.src = src; //真实图片,当赋值后img会发送请求去下载真实图片
}
}
})();
//设置真实图片
myImage.setSrc("https://www.baidu.com/img/bd_logo1.png"); //真实图片*/
//使用代理模式重构,把imgNode,img分开
//imgNode---显示
var myImage = (function(){
var imgNode = document.createElement("img"); //创建一个图片
document.body.appendChild(imgNode); //图片添加到body
return {
setSrc: function(src){ //变量
imgNode.src = src; //用来接收图片地址
}
}
})();
//图片代理
//var img = new Image();----空img---内存
//img.src = src; 给img赋,一旦赋值,它就会发送http请求下载图片
//img.onload ---下载完成就会触发该事件
var ProxyImage = (function(){ //ProxyImage就是return的结果
var img = new Image();
img.onload = function(){//当真实图片下载完成时触发 onload onclick
setTimeout(()=>{
myImage.setSrc(this.src);//用真实图片替换临时图片 this----img this----全局(闭包img)
}, 1000);
}
return {
setSrc2: function(src) {
myImage.setSrc("http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif"); //临时图片
img.src = src; //真实图片,当赋值后img会发送请求去下载真实图片
}
}
})();
ProxyImage.setSrc2("https://www.baidu.com/img/bd_logo1.png"); //真实图片
//0.先生成myImage, ProxyImage
//1.ProxyImage.setSrc设置真实的图片
//2.ProxyImage.setSrc设置真实图片同时指定临时图片
//3.临时图片会被立即添加到页面并显示
//4.img.src = src真实图片同时也在加载中。。。
//5.若真实图片加载完毕则通过myImage.setSrc替换临时图片
//return ----- 返回 ---需要返回值就需要return
</script>
</body>
</html>