JavaScript设计模式
设计模式的三大类:
- 创造型:
- 结构型:
- 行为型:
模块化模式--常用
一、构造器模式(或者说是构造函数模式)---不推荐使用这种方式
简单实现:
function Employee(name,age){ this.name = name this.age = age this.say = function(){ console.log(this.name + '-' + this.age) } } let employee1 = new Employee('zhangsan',19) let employee2 = new Employee('lisi',20) employee1.say() //zhangsan-19 employee2.say() //lisi-20 console.log(employee1,employee2)
缺点:每次创建对象的时候属性和方法都会重新开辟一份内存空间。会占用大量内存。
我们可以将say()方法放到原型上,这样多个构造函数的实例对象就可以共享一个原型。节省了内存空间
1.简单实现:将上面构造函数的say()方法放到原型上
function Employee(name,age){ this.name = name this.age = age } Employee.prototype.say = function(){ console.log(this.name + '-' + this.age) } let employee1 = new Employee('zhangsan',19) let employee2 = new Employee('lisi',20) employee1.say() //zhangsan-19 employee2.say() //lisi-20
2.es6写法class:将构造器和原型模式合二为一
class Employee { constructor(name,age){ this.name = name this.age = age } //类里面的方法是放在原型上的 say(){ console.log(this.name + '-' + this.age) } } let employee1 = new Employee('zhangsan',19) let employee2 = new Employee('lisi',20) employee1.say() //zhangsan-19 employee2.say() //lisi-20
注意:实例对象 employee1 本身没有say()方法,而是在原型上才有
3.适用场景
应用:选项卡切换(只需要传入元素名、和事件名)、轮播图
实例:
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <style> *{ margin: 0; padding: 0; } .container{ width: 500px; height: 300px; } ul,li{ list-style: none; } .header{ display: flex; width: 500px; } .header li { flex:1; width: 20px; line-height:20px; border: 1px solid #ddd; } .header li.active{ background-color: red; } .box{ position: relative; height: 200; } .box li{ position: absolute; top: 0; left: 0; width: 500px; height: 200px; background-color: yellow; display: none; } .box li.active{ display: block; } </style> <body>
<!--选项卡1 鼠标点击切换--> <div id="container1" class="container"> <ul class="header"> <li class="active">标题1</li> <li>标题2</li> <li>标题3</li> </ul> <ul class="box"> <li class="active">内容1</li> <li>内容2</li> <li>内容3</li> </ul> </div>
<!--选项卡2 鼠标移入切换--> <div id="container2" class="container"> <ul class="header"> <li class="active">标题1</li> <li>标题2</li> <li>标题3</li> </ul> <ul class="box"> <li class="active">内容1</li> <li>内容2</li> <li>内容3</li> </ul> </div> <script> class Tabs{ constructor(selector,type){ this.selector = document.querySelector(selector) this.headerItems = this.selector.querySelectorAll('.header li') this.boxItems = this.selector.querySelectorAll('.box li') this.type = type this.change() } } Tabs.prototype.change = function(){ for(let i = 0; i < this.headerItems.length; i++){ this.headerItems[i].addEventListener(this.type, () =>{ //移除所有的active for(let m = 0; m < this.headerItems.length; m++){ this.headerItems[m].classList.remove('active') this.boxItems[m].classList.remove('active') } this.headerItems[i].classList.add('active') this.boxItems[i].classList.add('active') },false) } } new Tabs("#container1",'click') new Tabs("#container2",'mouseover') </script> </body> </html>
三、工厂模式: 主要用来创建同一类对象,根据不同参数返回不同的实例
1.适用场景:后台用户权限管理(不同角色展示的页面不同)
2.缺点:我们要创建大量的对象时,会难以维护。所以简单工厂只能用于创建对象数量较少的,对象的创建逻辑不复杂时使用
3.案例:
es5方式:
function UserFactory(role){ function User(role,pages){ this.role = role; this.pages = pages; } switch(role){ case 'superadmin': return new User('superadmin',['home','user-manage','news-manage']) break; case 'admin': return new User('admin',['home','user-manage']) break; case 'user': return new User('editor',['home']) break; default: throw new Error('参数错误') } } let user1 = new UserFactory('admin') // {role:admin,pages:['home','user-manage']} let user2 = new UserFactory('user') // {role:user,pages:['home']} let user3 = new UserFactory('superadmin') // {role:superadmin,pages:['home','user-manage','news-manage']}
es6方式:
四、抽象工厂模式:不直接生成实例,而是对产品类的创建,要的是类名 ---js中这种模式应用的很少
1.实例场景:还是拿上面的权限管理例子,但每一种权限展示的对应的页面内容有所不同
2. 适用场景:需要创建多个相关或这依赖的对象
3.抽象工厂实现思路:需要设置一个手动抛出错误的方式来表明是一个抽象方法,由子类去继承并实现(重写)
4.代码实现:
// User 父类
class User { constructor(name,role,pages){ this.name = name; this.role = role; this.pages = pages } welcome(){ console.log('欢迎回来',this.name) } dataShow(){ //抽象方法,子类如果没有覆盖制定方法,则抛出错误 throw Error('抽象方法需要被重写') } }
// Superadmin 子类 class Superadmin extends User{ constructor(name){ super(name,'superadmin',['home','user-manage','news-manage']) } //抽象方法需要重写覆盖父类的方法---首页 dataShow(){ //abstrct 看到超级管理员的首页展示内容 console.log(this.name + '是超级管理员,可以看到',this.pages) } //子类自身独有的方法,用户管理页面 addUser(){ } //子类自身独有的方法---新闻管理页面 news(){ } }
//Admin 子类 class Admin extends User{ constructor(name){ super(name,'admin',['home','user-manage']) } //抽象方法需要重写覆盖父类的方法---首页 dataShow(){ //abstrct 看到管理员的首页展示内容 console.log(this.name + '是管理员,可以看到',this.pages) } //子类自身独有的方法---用户管理页面 addUser(){ } }
//Editor 子类 class Editor extends User{ constructor(name){ super(name,'editor',['home']) } dataShow(){ //abstrct 看到编辑人员的首页展示内容---首页 console.log(this.name + '是编辑人员,可以看到',this.pages) } } //抽象工厂模式 function getAbstrctUserFactor(role){ switch(role){ case 'superadmin': return Superadmin break; case 'admin': return Admin break; case 'editor': return Editor break; default: throw new Error('参数错误') } } let userClass = getAbstrctUserFactor('admin') let userClass2 = getAbstrctUserFactor('superadmin') let userClass3 = getAbstrctUserFactor('editor') let user = new userClass('张三') let user2 = new userClass2('李四') let user3 = new userClass2('王五') user.dataShow() // 张三是管理员,可以看到 ['home', 'user-manage'] user2.dataShow() // 李四是超级管理员,可以看到 ['home', 'user-manage', 'news-manage'] user3.dataShow() // 王五是超级管理员,可以看到 ['home', 'user-manage', 'news-manage']
五、建造者模式:将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示
1.使用场景:创建的对象比较复杂,又多个部分构成,各部分又面临复杂的变化,但构造顺序是稳定的
2. 建造者在开发中最常用的方式就是,当一个类构造器需要传入很多参数时。如果创建这个类的实例,代码可读性很差,而且很容易顺序传错。此时可以利用建造器模式进行重构
3. 应用场景:XHR对象创建(设置不同的属性和方法来构建不同类型的XHR对象)、DOM元素的创建(设置元素的属性、样式、子元素,然后将其添加到文档中)
3.实例:实例化一个user对象,但在创建过程需要传很多参数,并且只知道其中一部分参数信息,还需要按书序传入。建造者模式就可以很好的解决这个问题
class User{ constructor(name,sex,age,phone,email){ this.name = name || ''; this.sex = sex || '男'; this.age = age || 0; this.phone = phone || ''; this.email = email || '' } } //添加一个UserBuilder类,最后用build来实例化User class UserBuilder{ setName(name){ this.name = name return this } setSex(sex){ this.sex = sex return this } setAge(age){ this.age = age return this } setPhone(phone){ this.phone = phone return this } setEmail(email){ this.email = email return this } build(){ return new User(this.name,this.sex,this.age,this.phone,this.email) } } const user = new UserBuilder().setName('张三').setPhone(12121212323).build() const user2 = new UserBuilder() .setEmail(1234567@qq.com) .setName('李四') .setAge(19) .setPhone(13823234532) .build() console.log(user,user2)
User不变,只需要新添加一个UserBuilder类,User中需要的参数将在UserBuilder中拆分成单个方法来赋值,最后用 build() 来实例化User
1.单例模式实现思路:保证一个类只能被实例一次,如果该类已经创建则直接返回该实例,否则创建一个实例并保存
2 .优点:内存中只有一个实例,减少了内存开销。避免了对资源多重的占用
3.应用场景:常见的登录弹窗、new Vue()、jquery
4.例子:
class Singleton{ static instance = null //定义静态属性instance constructor(name,age){ if(!Singleton.instance){ this.name = name //实例属性 this.age = age; //实例属性 Singleton.instance = this //将实例对象 赋给 静态属性instance } return Singleton.instance } } let s1 = new Singleton('张三',18) let s2 = new Singleton('李四',30) console.log(s1,s2,s1===s2)
登录弹窗例子:
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <style> .modal{ position: fixed; width: 200px; height: 200px; line-height: 200px; text-align: center; top:50%; left: 50%; transform: translate(-50%,-50%); background-color:yellow; } </style> </head> <body> <button id="open">打开</button> <button id="close">关闭</button> <script> class Modal{ static instance = null constructor(){ if(!Modal.instance){ //创建弹窗 this.el = document.createElement('div') this.el.className = 'modal' this.el.innerHTML = '登录对话框' this.el.style.display = 'none' document.body.appendChild(this.el) Modal.instance = this //将实例对象 赋给 静态属性instance } return Modal.instance } show(){ this.el.style.display = 'block' } hide(){ this.el.style.display = 'none' } } document.getElementById('open').onclick = function(){ let modal = new Modal() modal.show() } document.getElementById('close').onclick = function(){ let modal = new Modal() modal.hide() } </script> </body> </html>
七、装饰器模式:在不改变原有对象的基础上,动态给对象添加额外的功能或者职责。灵活的替代了继承方式来扩展功能
1.适用场景:在不改变原来的功能,需要扩展额外的功能时
2.实际应用例子:在执行某个函数之前或者之后做一些额外的操作等等。使用before和after来拦截函数的执行
3.代码实现思路:在JS中函数是Function的实例,所以函数可以获取到Function原型上的方法
4.代码实现:
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <div id="btn">跳转</div> <script> Function.prototype.before = function(beforeFn){ let _this = this return function(){ // 先进行前置函数调用 beforeFn.apply(this,arguments) //执行原来的函数 return _this.apply(this,arguments) } } Function.prototype.after = function(afterFn){ let _this = this; return function(){ //执行原来的函数 let result = _this.apply(this,arguments) //执行后置函数 afterFn.apply(this,arguments) return result //将原来的函数调用的结果返回 } } function log(){ console.log('先上传uv,pv数据') } //重写render //函数是Function的实例,所以render函数可以获取到Function原型上的 before和after两个方法 function render(){ console.log('页面处理逻辑'); } //重写render render = render.before(log) btn.onclick = function(){ //更改需求:在处理页面逻辑之前,先上传uv,pv数据 render() } </script> </body> </html>
八、适配器模式:将一个类的接口转换成客户希望的另一个接口,使原本不能在一起工作的类能够协同工作
1.优点:让两个没有关联的类可以同时有效运行。提高了复用性以及灵活性
2. 缺点:过多的使用适配器会让系统变的凌乱,不易整体把控。建议轻易不要使用该模式
3. 实际案例:百度地图渲染的方法和高德地图渲染的方法不同,为了统一接口需要做兼容
//腾讯地图 class TencentMap{ constructor(){ } show(){ console.log('开始渲染腾讯地图') } } //百度地图 class BaiduMap{ constructor(){ } display(){ console.log('开始渲染百度地图') } } //为腾讯地图类 做适配器 class TencentAdapter extends TencentMap{ constructor(){ super() } display(){//增加需要适配的方法,使腾讯地图有display方法 this.show() } }
//渲染地图 function renderMap(map){ map.display() } renderMap(new TencentAdapter()) renderMap(new BaiduMap())
九、策略模式:定义一些算法或规则封装起来,目的是将使用和实现分离。且算法的变化不会影响使用的客户
1.优点:算法可以自由切换,避免使用多层if...else 判断,增加了扩展性
2.适用场景:主要用于需要动态选择不同的方式或者当if...else过多时,可以考虑使用策略模式
3.使用案例:例如一个流程列表,根据不同的流程的审批状态,展示不同的颜色和内容
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <style> ul,li{ margin: 0; padding: 0; } li{ display: flex; list-style: none; justify-content: space-between } .yellow{ background-color: yellow } .green{ background-color: green } .red{ background-color: red } </style> </head> <body> <ul id="list"> </ul> <script> //流程数据列表 let lists = [ { type: 1, //1.审核中 2.已通过 3.未通过 title:'请假流程' }, { type: 2,//1.审核中 2.已通过 3.未通过 title:'加班流程' }, { type: 3,//1.审核中 2.已通过 3.未通过 title:'请假流程' } ] //采用策略模式 let obj = { 1:{ content: '审核中', className: 'yellow' }, 2:{ content: '已通过', className: 'green' }, 3:{ content: '未通过', className: 'red' } } document.getElementById('list').innerHTML = lists.map(item=> `<li> <span>${item.title}</span> <span class="${obj[item.type].className}">${obj[item.type].content}</span> </li>` ).join('') </script> </body> </html>
1.适用场景:1)在访问目标对象之前或者之后添加日记记录、性能管控安全检查等等 2.)可以控制访问对象,或者访问的次数 3)可以隐藏实际实现的细节,降低复杂性
2.实际应用:简单实现数据驱动
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <div id="app"></div> <script>
//元数据 let vueobj = { data:'' } //对vueobj 做代理 let proxy = new Proxy(vueobj,{ get(target,key){ console.log('监听到获取') return target[key] }, set(target,key,value){ if(key === 'data'){ console.log('监听到设置') app.innerHTML = value //修改页面上的数据,操作dom } target[key] = value } }) //每次修改代理对象的数据,页面数据会改变 proxy.data = '测试数据2' </script> </body> </html>
十一、观察者模式:一对多的依赖关系,当一个主题对象(subject)的状态发生改变时,其他所有依赖着(或者说是观察者 Observer)都会收到通知
1.适用场景:当一个对象的状态变化需要同时更新其他对象时
2.优点:1.观察者和被观察者(目标)之间是抽象的耦合 2. 建立了一套状态改变时的触发和通知机制
3.缺点:观察者众多时,通知过程可能耗时
4.应用场景:根据点击的菜单添加面包屑
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <style> .box{ display: flex; height: 500px; } ul,li{ list-style: none; margin: 0; padding: 0; } .box .left{ width: 100px; text-align: center; background-color: skyblue; } .box .right{ flex: 1; background-color: yellow; } </style> </head> <body> <header class="header">路径:<span id="header"></span></header> <div class="box"> <ul class="left"> <li>首页</li> <li>列表</li> <li>关于</li> </ul> <div id="breadcrumbs" class="right"></div> </div> <script> class Subject{ constructor(){ this.observers = [] } add(observer){ this.observers.push(observer) } notify(data){ this.observers.forEach(item=> { item.update(data) }) } } class Observer{ constructor(select){ this.el = document.querySelector(select) } update(data){ this.el.innerHTML = data } } let sub = new Subject() let observe1 = new Observer('#breadcrumbs') let observe2 = new Observer('#header') sub.add(observe1) sub.add(observe2) let li = document.querySelectorAll('li') li.forEach(item=>{ item.onclick = function(){ sub.notify(this.innerHTML) } }) </script> </body> </html>
十二、发布订阅模式:发布者和订阅者不用互相知道,通过第三方实现调度,根据不同的类型(eg:click,mouseover等等)触发对应的订阅者。属于经过解耦的观察者模式
已上面观察者模式的例子,用发布订阅模式实现:
es5实现:
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <style> .box{ display: flex; height: 500px; } ul,li{ list-style: none; margin: 0; padding: 0; } .box .left{ width: 100px; text-align: center; background-color: skyblue; } .box .right{ flex: 1; background-color: yellow; } </style> </head> <body> <header class="header">路径:<span id="header"></span></header> <div class="box"> <ul class="left"> <li>首页</li> <li>列表</li> <li>关于</li> </ul> <div id="breadcrumbs" class="right"></div> </div> <script> /* * 1.观察者和目标要相互知道 * 2.发布者和订阅者不用互相知道,通过第三方实现调度,属于经过解耦合的观察者模式 **/ //publish 发布 //subscribe 订阅 //发布订阅 const PubSub = { message:{}, publish(type,data){ if(!this.message[type]){ return } this.message[type].forEach(item=>item(data)) }, //订阅事件 subscribe(type,cb){ if(!this.message[type]){ this.message[type] = [cb] }else{ this.message[type].push(cb) } }, //取消订阅 unsubscribe(type,cb){ if(!this.message[type]){ return } if(!cb){ this.message[type] && (this.message[type].length = 0) }else{ this.message[type] = this.message[type].filter(item => item !== cb) } } } function testA(data){ console.log('testa',data) document.getElementById('breadcrumbs').innerHTML = data } function testB(data){ console.log('testB',data) document.querySelector('#header').innerHTML = data } PubSub.subscribe('UpdateBread',testA) PubSub.subscribe('UpdateBread',testB) const oli = document.querySelectorAll('.left li') for(let i = 0; i < oli.length; i++){ oli[i].onclick = function(){ PubSub.publish('UpdateBread',this.innerHTML) } } </script> </body> </html>
es6实现:
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <style> .box{ display: flex; height: 500px; } ul,li{ list-style: none; margin: 0; padding: 0; } .box .left{ width: 100px; text-align: center; background-color: skyblue; } .box .right{ flex: 1; background-color: yellow; } </style> </head> <body> <header class="header">路径:<span id="header"></span></header> <div class="box"> <ul class="left"> <li>首页</li> <li>列表</li> <li>关于</li> </ul> <div id="breadcrumbs" class="right"></div> </div> <script> /* * 1.观察者和目标要相互知道 * 2.发布者和订阅者不用互相知道,通过第三方实现调度,属于经过解耦合的观察者模式 **/ //publish 发布 //subscribe 订阅 //发布订阅 class PubSub{ constructor(){ this.message = {} } publish(type,data){ if(!this.message[type]){ return } this.message[type].forEach(item=>item(data)) } //订阅事件 subscribe(type,cb){ if(!this.message[type]){ this.message[type] = [cb] }else{ this.message[type].push(cb) } } //取消订阅 unsubscribe(type,cb){ if(!this.message[type]){ return } if(!cb){ this.message[type] && (this.message[type].length = 0) }else{ this.message[type] = this.message[type].filter(item => item !== cb) } } } function testA(data){ document.getElementById('breadcrumbs').innerHTML = data } function testB(data){ document.querySelector('#header').innerHTML = data } let pubSub = new PubSub() pubSub.subscribe('UpdateBread',testA) pubSub.subscribe('UpdateBread',testB) const oli = document.querySelectorAll('.left li') for(let i = 0; i < oli.length; i++){ oli[i].onclick = function(){ pubSub.publish('UpdateBread',this.innerHTML) } } </script> </body> </html>
十三、模块化模式:是一种将代码分解为小而独立的方法,每个部分都有自己的职责和功能
1.优点:1)避免命名冲突(减少命名空间污染)2)更好的分离,按需加载 3)更好复用;特别是在大型系统的开发中,模块化开发更是必不可少
2.工作中的使用: 1)前后端请求的接口,可以根据不同的业务类型进行区分;2)webpack将.vue文件打包成.js文件,这个js文件就是一个模块化文件
3.具体实例:后台管理系统,根据业务的不同(订单、用户、商品)将请求的接口按模块拆分
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script type="module"> import {addProduct} from './product.js' import {fetchOrderList} from './order.js' addProduct({id:''}) fetchOrderList({orderid:''}) </script> </body> </html>
十四、桥接模式:是指抽象部分与它的实现部分分离,使它们各自独立变化,通过组合关系代替继承关系,降低抽象和实现两个可变维度的耦合度
1.优点: 抽象和实现分离
2.缺点:每使用一个桥接元素都要增加一次函数调用,这对应用程序会有一些负面影响---提高了系统的复杂度
3.适应场景: 不同弹窗不同动画
4.例子:页面有Toast、Message两种类型的弹窗,现实和隐藏可以使用不同的动画效果
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //构造函数 Toast function Toast(node,animation){ this.node = node this.animation = animation } Toast.prototype.show = function(){ this.animation.show(this.node) } Toast.prototype.hide = function(){ this.animation.hide(this.node) } //构造函数 Message function Message(node,animation){ this.node = node this.animation = animation } Message.prototype.show = function(){ this.animation.show(this.node) } Message.prototype.hide = function(){ this.animation.hide(this.node) } //动画效果 const Animations = { bounce: { show: function(node){ console.log(node + '弹跳着出现') }, hide: function(node){ console.log(node + '弹跳消失') } }, slide: { show: function(node){ console.log(node + '滑动出现') }, hide: function(node){ console.log(node + '滑动消失') } } } let toast1 = new Toast('元素1', Animations.bounce) let message1 = new Message('元素2', Animations.slide) toast1.show() message1.show() </script> </body> </html>
es6写法:
//Toast 类 class Toast { constructor(node,animation){ this.node = node; this.animation = animation } show(){ this.animation.show(this.node) } hide(){ this.animation.hide(this.node) } } //Message 类 class Message{ constructor(node,animation){ this.node = node this.animation = animation } show(){ this.animation.show(this.node) } hide(){ this.animation.hide(this.node) } } //动画效果 const Animations = { bounce: { show: function(node){ console.log(node + '弹跳着出现') }, hide: function(node){ console.log(node + '弹跳消失') } }, slide: { show: function(node){ console.log(node + '滑动出现') }, hide: function(node){ console.log(node + '滑动消失') } } } let toast1 = new Toast('元素1', Animations.bounce) let message1 = new Message('元素2', Animations.slide) toast1.show() message1.show()
十五、组合模式:将对象组合成树形结构,以表示“部分-整体”的层次结构。无需关心对象有多少层,调用时只需要在根部进行调用
1.使用场景:部分、整体场景,如:1)树形菜单; 2)文件、文件夹管理
2.例子:文件和文件夹的关系
class Folder { constructor(folder){ this.folder = folder this.lists = [] } add(folder){ this.lists.push(folder) } scan(){ console.log('开始扫描文件夹' + this.folder) for(let i =0;i < this.lists.length; i++){ this.lists[i].scan() } } } class File { constructor(file){ this.file = file } scan(){ console.log('开始扫描文件' + this.file) } } let rootfolder = new Folder('root') let cssfolder = new Folder('css') let jsfolder = new Folder('js') rootfolder.add(cssfolder) rootfolder.add(jsfolder) cssfolder.add(new File('index.css')) jsfolder.add(new File('index.js')) rootfolder.scan()
3.例子动态创建树形结构菜单
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <div id="root"></div> <script> class Folder { constructor(folder){ this.folder = folder this.lists = [] } add(folder){ this.lists.push(folder) } scan(){ let childUl = document.createElement('ul') if(this.folder !== 'root'){ let oul = document.createElement('ul') let oli = document.createElement('li') oli.innerHTML= this.folder oli.appendChild(childUl) oul.appendChild(oli) document.querySelector('#root').appendChild(oul) } for(let i = 0;i < this.lists.length; i++){ this.lists[i].scan(childUl) } } } class File { constructor(file){ this.file = file } scan(childUl){ let oli = document.createElement('li') oli.innerHTML= this.file childUl.appendChild(oli) } } //根 let rootfolder = new Folder('root') //一级菜单 let cssfolder = new Folder('用户管理') let jsfolder = new Folder('权限管理') rootfolder.add(cssfolder) rootfolder.add(jsfolder) //二级菜单 cssfolder.add(new File('添加用户')) cssfolder.add(new File('编辑用户')) jsfolder.add(new File('添加权限')) jsfolder.add(new File('编辑权限')) rootfolder.scan() </script> </body> </html>
十六、迭代器模式:是指提供一种方法顺序访问一个对象中的每个元素,并且不需要暴露该对象的内部
迭代分为内部迭代和外部迭代
1.内部迭代:内部已经定义好了迭代规则,它完全接受整个迭代过程,外部只需要一次初始调用
const MyEach = function(arr,fn){ for(let i = 0; i < arr.length; i++){ fn(arr[i],i) } } MyEach([1,2,3],(value,key) => { console.log(key,value) });
2.外部迭代:外部手动控制迭代下一个数据项。
实现外部迭代器应拥有 next属性,执行next()会返回一个包含 包含value 和 done的对象
例子:
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //外部迭代 var Iterator = function(obj){ var current = 0, next = function(){ current++ }, isDone = function(){ return current >= obj.length }, getCurrentItem = function(){ return obj[current] } return { next, isDone, getCurrentItem } } //比较函数 var compareAry = function(iterator1,iterator2){ while(!iterator1.isDone() && !iterator2.isDone()){ if(iterator1.getCurrentItem() !== iterator2.getCurrentItem()){ throw new Error('不想等') } iterator1.next() iterator2.next() } console.log('相等') } compareAry(new Iterator([1,2,3]),new Iterator([1,2,4])) </script> </body> </html>
十六、职责链模式:在职责链中,请求会依次经过多个处理者,如果当前处理者能够处理请求,则将请求处理完成,否则将请求传递给下一个处理者,直到最后都没有能处理请求,那么请求将被忽略
1.优点:简化代码,避免使用复杂的if-else或者 switch 语句;可以动态的添加或移除,是系统更加灵活和可配置
2.缺点:每个处理器都需要执行一次处理函数,可能会导致一些性能问题;处理器过于复杂,可能hi导致代码难以理解和维护,因此,该模式应该谨慎使用
3.适应场景:处理器之间顺序要求
4.例子:输入框校验
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <input id="input" type="text"> <button id="btn">注册</button> <script> btn.onclick = function(){ checks.check() } function checkEmpty(){ if(input.value.length === 0){ console.log('这里不能为空') return true } return 'next' } function checkNum(){ if(Number.isNaN(+input.value)){ // 字符串前面加 + 会转换成数值类型 console.log('这里必须是数字') return true } return 'next' } function checkLength(){ if(input.value.length < 6){ console.log('这里必须大于6个数字') return true } return 'next' } class Chain { constructor(fn){ this.checkRule = fn this.nextRule = null } addRule(nextRule){ this.nextRule = new Chain(nextRule) console.log(nextRule,'nextRule') return this.nextRule } check(){ this.checkRule() === 'next' ? this.nextRule.check() : null } end(){ this.nextRule = { check: ()=> 'end' } } } let checks = new Chain(checkEmpty) checks.addRule(checkNum).addRule(checkLength).end() </script> </body> </html>