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方式:


     class User {
        constructor(role,pages){
            this.role = role;
            this.pages = pages;
        }
        static UserFactory(role) {
            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','news-manage'])
                    break;
                default:
                    throw new Error('参数错误')
            }
        }
    }
    //调用接口返回用户角色
    let role = 'admin' //superadmin、admin、editor
    let user = User.UserFactory(role)
    console.log(user)

四、抽象工厂模式:不直接生成实例,而是对产品类的创建,要的是类名 ---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>

 

  

 

      

    

posted @ 2024-09-10 10:08  yangkangkang  阅读(5)  评论(0编辑  收藏  举报