不知道怎么提高代码可扩展性?来看看优秀框架源码中的这几种设计模式吧! (3)

下面是代码:

// 先把之前的发布订阅模式拿过来 class PubSub { constructor() { this.events = {} } subscribe(event, callback) { if(this.events[event]) { this.events[event].push(callback); } else { this.events[event] = [callback] } } publish(event, ...args) { const subscribedEvents = this.events[event]; if(subscribedEvents && subscribedEvents.length) { subscribedEvents.forEach(callback => { callback.call(this, ...args); }); } } unsubscribe(event, callback) { const subscribedEvents = this.events[event]; if(subscribedEvents && subscribedEvents.length) { this.events[event] = this.events[event].filter(cb => cb !== callback) } } } // 实例化一个事件中心 const pubSub = new PubSub(); // 总共有 初始化页面 -> 获取最终结果 -> 运动效果 -> 运动控制 四个模块 // 初始化页面 const domArr = []; function initHTML(target) { // 总共10个可选奖品,也就是10个DIV for(let i = 0; i < 10; i++) { let div = document.createElement('div'); div.innerHTML = i; div.setAttribute('class', 'item'); target.appendChild(div); domArr.push(div); } } // 获取最终结果,也就是总共需要转几次,我们采用一个随机数加40(4圈) function getFinal() { let _num = Math.random() * 10 + 40; return Math.floor(_num, 0); } // 运动模块,具体运动方法 function move(moveConfig) { // moveConfig = { // times: 10, // 本圈移动次数 // speed: 50 // 本圈速度 // } let current = 0; // 当前位置 let lastIndex = 9; // 上个位置 const timer = setInterval(() => { // 每次移动给当前元素加上边框,移除上一个的边框 if(current !== 0) { lastIndex = current - 1; } domArr[lastIndex].setAttribute('class', 'item'); domArr[current].setAttribute('class', 'item item-on'); current++; if(current === moveConfig.times) { clearInterval(timer); // 转完了一圈广播事件 if(moveConfig.times === 10) { pubSub.publish('finish'); } } }, moveConfig.speed); } // 运动控制模块,控制每圈的参数 function moveController() { let allTimes = getFinal(); let circles = Math.floor(allTimes / 10, 0); let stopNum = allTimes % circles; let speed = 250; let ranCircle = 0; move({ times: 10, speed }); // 手动开启第一次旋转 // 监听事件,每次旋转完成自动开启下一次旋转 pubSub.subscribe('finish', () => { let time = 0; speed -= 50; ranCircle++; if(ranCircle <= circles) { time = 10; } else { time = stopNum; } move({ times: time, speed, }) }); } // 绘制页面,开始转动 initHTML(document.getElementById('root')); moveController();

上述代码的难点就在于运动模块的运动是异步的,需要在每圈运动完了之后通知运动控制模块进行下一次转动,观察者模式很好的解决了这个问题。本例完整代码我已经上传到我的GitHub了,可以去拿下来运行下玩玩。

装饰器模式

装饰器模式针对的情况是我有一些老代码,但是这些老代码功能不够,需要添加功能,但是我又不能去改老代码,比如Vue 2.x需要监听数组的改变,给他添加响应式,但是他又不能直接修改Array.prototype。这种情况下,就特别适合使用装饰者模式,给老方法重新装饰下,变成一个新方法来使用。

基本结构

装饰器模式的结构也很简单,就是先调用一下原来的方法,然后加上更多的操作,就是装饰一下。

var a = { b: function() {} } function myB() { // 先调用以前的方法 a.b(); // 再加上自己的新操作 console.log('新操作'); } 实例:Vue数组的监听

熟悉Vue响应式原理的朋友都知道(不熟悉的朋友可以看这里),Vue 2.x对象的响应式是通过Object.defineProperty实现的,但是这个方法不能监听数组的改变,那数组怎么监听的呢?数组操作一般就是push,shift这些方法,这些方法是数组原生的方法,我们当然不能去改他,那会了装饰器模式,我们完全可以在保持他之前功能的基础上给他扩展功能:

var arrayProto = Array.prototype; // 先拿到原生数组的原型 var arrObj = Object.create(arrayProto); // 用原生数组的原型创建一个新对象,免得污染原生数组 var methods = ['push', 'shift']; // 需要扩展的方法,这里只写了两个,但是不止这两个 // 循环methods数组,扩展他们 methods.forEach(function(method) { // 用扩展的方法替换arrObj上的方法 arrObj[method] = function() { var result = arrayProto[method].apply(this, arguments); // 先执行老方法 dep.notify(); // 这个是Vue的方法,用来做响应式 return result; } }); // 对于用户定义的数组,手动将它的原型指向扩展了的arrObj var a = [1, 2, 3]; a.__proto__ = arrObj;

上述代码是从Vue源码精简过来的,其实就是一个典型的使用装饰器扩展原有方法的功能的例子,因为Vue只扩展了数组方法,如果你不通过这些方法,而是直接通过下标来操作数组,响应式就不起作用了。

实例:扩展已有的事件绑定

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssdzz.html