最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript系列 -- 设计模式

    正文概述 掘金(ALKAOUA)   2021-05-18   513

    前言

    设计模式是一种思想,就像是前人记录下来的经验,就好比是厨师日积月累的经验撰写出来的菜谱

    这是定义:

    优雅是指不粗暴,简洁是因为规范化了,我们需要在日常开发中潜意识里去用这样一种思想,这样团队协作,后期的维护、迭代等等之类的会避免不少麻烦

    这里只介绍在 JavaScript 中常见的几种设计模式,其他的设计模式详见 参考文章

    单例模式

    应用场景

    一个 button 的点击事件是触发弹窗,那无论用户是否重复点击,弹窗只会创建一个

    定义

    具体实现

    实现的方法为先判断实例存在与否,如果存在则 直接返回,如果不存在就 创建了再返回,这就确保了一个类只有一个实例对象。

    其中利用了:ES6 的 let 不允许重复声明的特性

    方法一:if...else 来判断是否已经存在

    let Singleton = function(name){
        this.name = name;
        this.instance = null; 
    }
    Singleton.prototype.getName = function(){
        console.log(this.name);
    }
    Singleton.getInstance = function(name){
        if(this.instace){
            return this.instance; 
        }
        return this.instance = new Singleton(name);
    }
    
    let winner = Singleton.getInstance("winner");
    console.log(winner.getName());   // winner
    let sunner = Singleton.getInstance("sunner");
    console.log(sunner.getName());   // winner
    

    缺点:创建对象 的操作和 判断实例 的操作耦合在一起,并不符合“单一职责原则

    方法二:利用自执行函数,将创建对象和判断实例分开

    let ProxyCreateSingleton = (function(){
        let instance = null;
        return function(name){
            if(instance){
                return instance
            }
            return instance = new Singlton(name);
        }
    })();
        
    let Singlton = function(name){
        this.name = name;
    } 
    Singlton.prototype.getName = function(){
        console.log(this.name);
    }
    
    let winner = new ProxyCreateSingleton("winner");
    console.log(winner.getName());   // winner
    let sunner = new ProxyCreateSingleton("sunner");
    console.log(sunner.getName());   // winner
    

    上面的代码中,ProxyCreateSingleton()只负责 判断实例,Singlton只负责 创建对象和赋值

    策略模式

    应用场景

    举个非常简单的例子,一个检验表单的程序:用户输入姓名、性别、年龄、身份证号、手机号等等信息后提交表单,我们首先要检查哪一项没有填写,然后再各自检查输入内容是否合法。

    定义

    具体实现

    1. 使用策略模式前:一大堆的if...return...

    var checkAuth = function(data){
        if(data.name === null){
            console.log("姓名项不能为空") // 当然里面还可以继续写其他对结果的处理代码,这里省略了
        }
        if(data.gender === null){
            console.log("性别项不能为空")
        }
        if(data.age === null){
            console.log("年龄项不能为空")
        }
        if(data.id === null){
            console.log("您未输入身份证号")
        }
        if(data.phoneNumber === null){
            console.log("您未输入手机号")
        }
    }
    

    缺点:

    • 时间上不够优化
    • checkAuth 函数会爆炸 ?
    • 策略项无法复用
    • 违反开闭原则

    2. 使用策略模式后:obj + function

    /*策略类*/
    var checkObj = {
        "name": function(value) {
            if(!value) return "姓名项不能为空";
        },
        "gender" : function(value) {
            if(!value) return "性别项不能为空";
        },
        "age" : function(value) {
            if(!value) return "年龄项不能为空";
        },
        "id" : function(value) {
            if(!value) return "您未输入身份证号";
        },
        "phoneNumber" : function(value) {
            if(!value) return "您未输入手机号";
        }
    };
    
    /*环境类*/
    var calculateBouns = function(data) {
        var arr = Object.entires(data)
        for(let i in arr){
            var res = checkObj[ arr[i][0] ]( arr[i][1] )
            console.log(res)
        }
    };
    
    // 还能只单独调用其中一个方法
    var res = checkObj["age"]("");
    console.log(res) // "年龄项不能为空"
    

    这里只是简单举例,在 data 对象里直接拿取其属性去 checkObj 对象里面找由其命名的方法,然后传入对应属性的值,即可快速抵达。然后后期对各个方法修改起来也方便

    这是两种方式的对比:

    JavaScript系列 -- 设计模式

    发布 - 订阅模式

    应用场景

    需求 : 申请成功后,需要触发对应的订单、消息、审核模块对应逻辑

    JavaScript系列 -- 设计模式

    还有其他类似场景:怎么模仿 报社发报的流程 做 公众号推文发布和通知 的功能、小程序抽奖结果通知等等

    定义

    具体实现

    1. 使用发布 - 订阅模式前:

    一个函数包裹三个函数

    function applySuccess() {
        MessageCenter.fetch(); // 通知消息中心获取最新内容
        Order.update(); // 更新订单信息
        Checker.alert(); // 通知相关方审核
    }
    

    缺点:

    • 如果上面的函数还没完成 / 有错误,则下面的函数就会被卡死(无法运行、无法调试)
    • 本应该是并行处理的,结果是串行处理

    2. 使用发布 - 订阅模式后:

    JavaScript系列 -- 设计模式

    实现思路

    1. 创建一个对象
    2. 在该对象上创建一个缓存列表(调度中心)
    3. on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)
    4. emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
    // 公众号对象
    let eventEmitter = {};
    
    // 缓存列表,存放 event 及 fn
    eventEmitter.list = {};
    
    // 订阅
    eventEmitter.on = function (event, fn) {
        let that = this;
        // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
        // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
        (that.list[event] || (that.list[event] = [])).push(fn);
        return that;
    };
    
    // 发布
    eventEmitter.emit = function () {
        let that = this;
        // 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
        let event = [].shift.call(arguments),
            fns = [...that.list[event]];
        console.log(event) // 'notice'
        console.log(fns) // [f user1(content), f user2(content), f user3(content)]
        // 如果缓存列表里没有 fn 就返回 false
        if (!fns || fns.length === 0) {
            return false;
        }
        // 遍历 event 值对应的缓存列表,依次执行 fn
        fns.forEach(fn => {
            fn.apply(that, arguments);
        });
        return that;
    };
    
    function user1 (content) {
        if(content === 'success') console.log('收到申请成功的通知,开始执行 MessageCenter.fetch 函数');
    };
    function user2 (content) {
        if(content === 'success') console.log('收到申请成功的通知,开始执行 Order.update 函数');
    };
    function user3 (content) {
        if(content === 'success') console.log('收到申请成功的通知,开始执行 Checker.alert 函数');
    };
    
    // 订阅者发起订阅
    eventEmitter.on('notice', user1);
    eventEmitter.on('notice', user2);
    eventEmitter.on('notice', user3);
    
    // 发布者发布内容
    eventEmitter.emit('notice', 'success');
    

    JavaScript系列 -- 设计模式

    补充 once 、off 方法

    1. off 方法可以根据 event 值取消订阅(取消订阅)
    2. once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)
    let eventEmitter = {
        // 缓存列表
        list: {},
        // 订阅
        on (event, fn) {
            let _this = this;
            // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
            // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
            (_this.list[event] || (_this.list[event] = [])).push(fn);
            return _this;
        },
        // 监听一次
        once (event, fn) {
            // 先绑定,调用后删除
            let _this = this;
            function on () {
                _this.off(event, on);
                fn.apply(_this, arguments);
            }
            on.fn = fn;
            _this.on(event, on);
            return _this;
        },
        // 取消订阅
        off (event, fn) {
            let _this = this;
            let fns = _this.list[event];
            // 如果缓存列表中没有相应的 fn,返回false
            if (!fns) return false;
            if (!fn) {
                // 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空
                fns && (fns.length = 0);
            } else {
                // 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
                let cb;
                for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
                    cb = fns[i];
                    if (cb === fn || cb.fn === fn) {
                        fns.splice(i, 1);
                        break
                    }
                }
            }
            return _this;
        },
        // 发布
        emit () {
            let _this = this;
            // 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
            let event = [].shift.call(arguments),
                fns = [..._this.list[event]];
            // 如果缓存列表里没有 fn 就返回 false
            if (!fns || fns.length === 0) {
                return false;
            }
            // 遍历 event 值对应的缓存列表,依次执行 fn
            fns.forEach(fn => {
                fn.apply(_this, arguments);
            });
            return _this;
        }
    };
    
    function user1 (content) {
        console.log('用户1订阅了:', content);
    }
    function user2 (content) {
        console.log('用户2订阅了:', content);
    }
    function user3 (content) {
        console.log('用户3订阅了:', content);
    }
    function user4 (content) {
        console.log('用户4订阅了:', content);
    }
    
    // 订阅
    eventEmitter.on('article1', user1);
    eventEmitter.on('article1', user2);
    eventEmitter.on('article1', user3);
    
    // 取消user2方法的订阅
    eventEmitter.off('article1', user2);
    
    eventEmitter.once('article2', user4)
    
    // 发布
    eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
    eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
    eventEmitter.emit('article2', 'Javascript 观察者模式');
    eventEmitter.emit('article2', 'Javascript 观察者模式');
    
    // eventEmitter.on('article1', user3).emit('article1', 'test111');
    

    JavaScript系列 -- 设计模式

    由打印结果可看出:

    • user2 虽然 on 订阅了但是又 off 取消了订阅,所以发布者发布的所有内容 user2 全都没有收到
    • 只有 user4 订阅了 article2 ,所以虽然发布者发布了两次 article2 的内容,但是 user4 只收到一次

    观察者模式

    应用场景

    应用场景与发布-订阅模式类似

    定义

    可以理解为:一个班里的学生们都在听老师讲课,当老师布置任务时,会通知学生们都去执行

    具体实现

    // 被观察者
    function Subject(){
        this.observers = [];
    }
    Subject.prototype = {
        // 添加观察者
        add: function(observer) {
            this.observers.push(observer);
        },
        // 通知观察者
        notify: function(){
            var observers = this.observers;
            var len = observers.length;
            for(var i=0; i<len; i++){
                observers[i].update();
            }
        },
        // 移除观察者
        remove: function(observer) {
            var observers = this.observers;
            var len = observers.length;
            for(var i=0; i<len; i++){
                if(observers[i] === observer) {
                    observers.splice(i, 1);
                }
            }
        }
    }
    
    // 观察者
    function Observer(name) {
        this.name = name;
    }
    Observer.prototype = {
        // 观察者监听到变化后要处理的逻辑
        update: function(){
            console.log('收到通知,我是观察者:', this.name);
        }
    }
    
    // 创建观察者
    var observer1 = new Observer('John');
    var observer2 = new Observer('Alice');
    
    // 添加观察者
    var subject = new Subject();
    subject.add(observer1);
    subject.add(observer2);
    
    // 发起通知
    subject.notify();
    

    JavaScript系列 -- 设计模式

    发布-订阅模式与观察者模式的区别

    JavaScript系列 -- 设计模式

    简单来说,发布订阅模式观察者模式 多了一层 Event Channel

    观察者模式:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。

    发布订阅模式:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

    差异:

    • 观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。而在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
    • 发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
    • 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
    • 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅模式更像交叉应用模式。

    参考文章

    • JavaScript设计模式——单例模式
    • JavaScript 发布-订阅模式
    • 前端渣渣唠嗑一下前端中的设计模式(真实场景例子)
    • JavaScript设计模式
    • JavaScript 中常见设计模式整理

    起源地下载网 » JavaScript系列 -- 设计模式

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元