十分钟掌握JS设计模式

娓娓而谈
• 阅读 1053

JS 设计模式

面向对象思想:封装、继承、多态

1. 构造器模式

无非就是继承来实现的啦 - -!

构造器模式案例省略...

思考:为什么ES5的继承要写在 prototype 中,而不是直接写在构造方法里?

答:写在构造器中,无法做到数据共享(会造成资源浪费)

  • 所以 prototype 中会存放需要共享数据的方法和属性(基本上都是方法)
  • 而构造器中会存放不需要共享的属性和方法

2. 模块化模式

模块化嘛:AMD/UMD/CommonJS/Module 都是模块化,对象、闭包也是模块化

在JS中,模块化模式其实是模拟了"类"的概念。好处是有私密空间,不会造成全局污染。

虽然JS没有私有属性。但闭包能很好的实现私有属性的概念

简单的模块化模式:(闭包 - -.!)

    var module = (function () {
        var num = 0;

        return {
            getNum: function () {
                return num;
            },
            addNum: function () {
                return num++;
            }
        };

    })();

    console.log(module)
    
    module.addNum()

    console.log(module.getNum())

3. 单例模式

单例:就是限制一个类只能有一个实例化对象

使用场景案例:“警告/确认/提示弹窗”(只能存在一个的情况)

最简单的单例::闭包 + Flag 来实现

    const mySingleton = (function () {
        let _instance;

        return function () {
            if (!_instance) {
                _instance = {
                    x: 1,
                    setX: (arg) => {
                        _instance.x = arg;
                    }
                };
            }
            return _instance;
        }
        
    })()

    const instanceA = mySingleton();
    const instanceB = mySingleton();

    console.log(instanceA === instanceB);

封装一下:(PS: ES5的new如果有return使用return的值)

    const Singleton = (function() {
        var _instance;
        return function(obj) {
            return _instance || (_instance = obj);
        }
    })();

    var a = new Singleton({x: 1});
    var b = new Singleton({y: 2});

    console.log(a === b);

观察者模式

由观察者和观察者组成。通过观察者调用被观察者的实例。

观察者模式:观察者对象和被观察者对象 之间的订阅和触发事件

使用场景案例:“Vue 双向绑定实现”

简单的观察者模式: (仿 Vue 实现)

    // 观察者
    class Dep {
        constructor() {
            this.subs = []
        }
        
        addSub(sub) {
            this.subs.push(sub)
        }
        
        depend() {
            if (Dep.target) { 
                Dep.target.addDep(this);
            }
        }
        
        notify() {
            this.subs.forEach(sub => sub.update())
        }
    }
    
    // 被观察者
    class Watcher {
        constructor(vm, expOrFn) {
            this.vm = vm;
            this.getter = expOrFn;
            this.value;
        }

        get() {
            Dep.target = this;
            
            var vm = this.vm;
            var value = this.getter.call(vm, vm);
            return value;
        }

        evaluate() {
            this.value = this.get();
        }

        addDep(dep) {
            dep.addSub(this);
        }
        
        update() {
            console.log('更新, value:', this.value)
        }
    }
    
    // 观察者实例
    var dep = new Dep();
    
    //  被观察者实例
    var watcher = new Watcher({x: 1}, (val) => val);
    watcher.evaluate();
    
    // 观察者监听被观察对象
    dep.depend()
    
    dep.notify()

发布/订阅者模式

由订阅者 Subscriber 和发布者 Publisher 组成。

发布/订阅者模式:是观察者模式的变体,比观察者模式多了一个调度中心

  • 发布者发布信息到调度中心
  • 调度中心和订阅者直接完成订阅和触发事件事件

使用场景案例:“DOM 的 addEventListener 事件”

一个简单的发布/订阅者模式实现:(仿 EventBus 实现)

    // EventTarget 就是一个调度中心

    class EventTarget {
        constructor() {
            this.dep = {}
        }
        
        on(key, fn) {
            this.dep[key] = fn;
        }
        
        emit(key) {
            typeof this.dep[key] === 'function' ? this.dep[key]() : ''
        }
    }
    
    let eventTarget = new EventTarget()
    
    eventTarget.on('click', function() {console.log(1)})
    eventTarget.emit('click')

中介者模式

中介:撮合多个卖家 和 多个买家
    class Saler {
        constructor(name, cost) {
            this.name = name;
            this.cost = cost;
        }
            
        send() {
            console.log(`${cost}元出售${name}`)
        }
    }
    
    class Agency {
        constructor() {
            this.cargos = []
        }
        
        register(saler) {
            this.cargos.push(saler);
        }
        
        query(name) {
            const matchCargos = this.cargos.filter(cargo => cargo && cargo.name === name);
            if (matchCargos.length) {
                console.log(`查询到正在出售的商品:${JSON.stringify(matchCargos)}`)
            } else {
                console.log(`没有${name}在出售`);
            }
        }
    }
    
    let agency = new Agency();
    
    agency.query('cart');
    
    const cartA = new Saler('cart', '100');
    const cartB = new Saler('cart', '300');
    const house = new Saler('house', '500');

    agency.register(cartA);
    agency.register(cartB);
    agency.register(house);
    
    agency.query('cart');
    agency.query('house');
    agency.query('ABC');

命令模式

为方法的调用进行解耦
    const command = {
        buy(name, cost) {
            console.log(`购买${name}消费了${cost}元`)
        },
        sale(name, cost) {
            console.log(`出售了${name}赚得${cost}元`)
        },
        say(name, cost) {
            console.log(`这里${cost}元可以买到${name}`)
        },
        execute(fnName) {
            const fn= this[fnName];
            (typeof fn === 'function') && fn.apply(this, [].slice.call(arguments, 1))
        }
    }
    
    command.execute('buy', 'VIP', '200');
    command.execute('sale', '节操', '998');
    command.execute('say', 'VIP', '123');

策略模式

策略模式最大的好处是:减少if-else的使用,同时增加代码可读性

简单的年终奖计算。(策略模式放在必填项/规则验证会很便捷)

    // 策略模式
    const bonus = {
        A: function(base) {
            return base * 4;
        },
        B: function(base) {
            return base * 3;
        },
        C: function(base) {
            return base * 2;
        },
        D: function(base) {
            return base;
        }
    }

    const level = "B";
    const base = "1008611";
    const yearBouns = bonus[level](base);
    console.log(yearBouns)

工厂模式

一个工厂(类) 能生产各种零件(实例)
简单的工厂模式

通过一个类获取不同类的实例

    class Cat {}
    class Dog {}
    class Pig {}
    
    function Factory(type, args) {
        switch (type){
            case 'cat':
                return new Cat(args);
                break;
            case 'dog':
                return new Dog(args);
                break;
            default:
                return new Pig(args);
                break;
        }
    }
    
    const cat = new Factory('cat', {name: 'cat'});
    const dog = new Factory('dog', {name: 'dog'});
    const pig = new Factory('pig', {name: 'pig'});
    
    console.log(cat, dog, pig)
抽象工厂模式

通过继承抽象的类(含有未实现的方法)、结合简单工厂模式,生成抽象工厂

抽象工厂的好处:通用方法写在工厂函数中,不需要重复实现,不同个性化代码在子类中实现

实现:省略...

复杂工厂模式

允许工厂产生的不同零件一起工作:

    class Wheel {
        turn() {
            console.log('轮子开始转动啦');
        }
    }
    
    class Oil {
        warn() {
            console.log('汽油不足')
        }
    }
    
    class Cart {
        constructor() {
            this.cart = {}
        }
        
        getPart(name, args) {
            return this.cart[name] ? new this.cart[name](args) : null;
        }
        
        setPart(name, Part) {
            this.cart[name] = Part;
        }
    }
    
    const cart = new Cart();
    
    cart.setPart('wheel', Wheel)
    cart.setPart('oil', Oil)
    
    const wheel = cart.getPart('wheel', {name: '轮子A'});
    const oil = cart.getPart('oil', {name: '汽油A'});
    
    wheel.turn();
    oil.warn();

修饰器模式

修饰:不改变原有对象,在其基础上进行拓展

基本上每天都在用的设计模式...

简单的修饰模式实现:

    const after = function(fn, afterFn) {
        return function() {
            fn.apply(this, arguments)
            afterFn.apply(this, arguments)
        }
    }

    const myAfter = after(after(fn1, fn2), fn3)
    myAfter()

最后

推荐关注我的微信公众号,每天推送高质量文章,我们一起交流成长

十分钟掌握JS设计模式

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
JS必知的6种继承方式
JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?让我们拭目以待JS继承的实现方式既然要实现继承,那么首先我们得有一个父类,代码如下:// 父类function Person(name) { // 给构造函数添加了参数  this.name  name;
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的
Stella981 Stella981
3年前
Javascript继承5:如虎添翼
/寄生式继承其实就是对原型继承的第二次封装,在封装过程中对继承的对象进行了扩展。也存在原型继承的缺点!!这种思想的作用也是为了寄生组合式继承模式的实现。///声明基对象varbook{name:'jsbook',al
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这