微信搜索【前端食堂】你的前端食堂,记得按时吃饭。

本文已收录在前端食堂 Github https://github.com/Geekhyt/front-end-canteen,感谢Star。

有人问我坚持写博客的原因是什么?

借用著名小说家斯蒂芬·金的一句话:“开始写吧!年轻人。”

当你把消费级兴趣升级为生产型兴趣时,你才会渐渐发现以前没有窥见的门道和妙处。

咳咳,天凉了请喝鸡汤~

可能本系列文章中所讲的设计模式你在工作中经常应用它们,但是并不知道它们的名字。

一旦我们将这些设计模式整理学习并融会贯通后,便可以大大增强我们的编程功底,在遇到实际业务需求时,给我们提供更好的解决问题的思路。

毕竟我们的口号是:不加班!

书接上文,本文给大家介绍的是结构型设计模式,包括外观模式适配器模式代理模式装饰者模式桥接模式组合模式以及享元模式

外观模式 Facade

概念:可以对复杂的子系统接口提供一个更高级的统一接口,对底层结构兼容性做统一封装来简化用户使用。

这种模式比较简单也比较容易理解,在日常的开发中你一定遇到过以下的场景。

那些年我们对ie浏览器做过的兼容。。

这些常见的兼容方案属于外观模式。

var getEvent = function (event) {return event || window.event;
}var getTarget = function (event) {var event = getEvent(event);return event.target || event.srcElement;
}var preventDefault = function (event) {var event = getEvent(event);if (event.preventDefault) {event.preventDefault();} else {event.returnValue = false;}
}var stopBubble = function (event) {var event = getEvent(event);if (event.stopPropagation) {event.stopPropagation();     } else {event.cancelBubble = true;}
}

在团队开发中,为了避免添加点击事件时出现的重名覆盖问题,我们会使用DOM2级事件处理程序绑定事件,并添加对浏览器的兼容。

function addEvent (dom, type, fn) {if (dom.addEventListener) {dom.addEventListener (type, fn, false);} else if (dom.attachEvent) {// 兼容IE(低于9)dom.attachEvent('on' + type, fn);} else {dom['on' + type] = fn;}
}

你当然也可以通过外观模式,来封装多个功能来简化底层操作方法。

var T = {g : function (id) {return document.getElementById(id);},css : function (id, key, value) {document.getElementById(id).style[key] = value;},attr : function (id, key, value) {document.getElementById(id)[key] = value;},html : function (id, html) {document.getElementById(id).innerHTML = html;},on : function (id, type, fn) {document.getElementById(id)['on' + type] = fn;}
};T.css('box', 'background', 'red');
T.attr('box', 'className', 'box');
T.html('box', '这是新添加的内容');
T.on('box', 'click', function() {T.css('box', 'width', '500px');
})

外观模式对接口进行了包装,我们使用时无须知道接口实现的具体细节,按照规则使用即可。

适配器模式 Adapter

概念:将一个类(对象)的接口(方法或者属性)转化成另外一个接口,以满足用户需求,使类(对象)之间接口的不兼容问题通过适配器得以解决。

适配器在我们的日常生活中很常见,比如出国旅行时,有的国家只有三项的插座,这时候我们需要三项转两项插头电源适配器。

再比如iPhone7以后耳机接口变成了lightning接口,为了适配圆孔耳机苹果为我们提供了适配器。

// 为两个代码库所写的代码兼容运行而书写的额外代码是适配器的一种。
window.A = A = jQuery;

jQuery中的适配器

jQuery.fn.css()
jQuery核心cssHook
// (为了控制文数,此处不贴代码,阅读完后大家可自行去官网查看)

jQuery源码

https://user-gold-cdn.xitu.io/2019/10/9/16dac404cd3ae3ef?w=530&h=504&f=jpeg&s=47028

参数适配器

function doSomeThing(name, title, age, color, size, prize) {}
// 我们记住这些参数的顺序是很困难的,所以我们经常是以一个参数对象方式来传入
/**
* obj.name : name
* obj.title : title
* obj.age : age
* obj.color : color
* obj.size : size
* obj.prize : prize
**/
// 当调用它的时候我们要考虑到传递的参数是否完整的问题,如果有一些必须参数没有传入
// 一些参数是有默认值的等等。这时我们可以用适配器来适配传入的参数对象
function doSomeThing (obj) {var _adapter = {name : '前端食堂',title : '设计模式',age : 24,color : 'blue',size : 100,prize : 99};for (var i in _adapter) {_adapter[i] = obj[i] || _adapter[i];}// 或者extend(_adapter, obj) 注: 此时可能会多添加属性// do things
}

很多插件对于参数配置都是这么做的。

数据适配

var arr = ['前端食堂', 'restaurant', '记得按时吃饭', '9月30日'];// 我们发现数组中每个成员代表的意义不同,所以这种数据结构 语义不好,我们将其适配成对象。function arrToObjAdapter (arr) {return {name : arr[0],type : arr[1],title : arr[2],data : arr[3]}var adapterData = arrToObjAdapter(arr);console.log(adapterData);  // {name:"前端食堂", type:"restaurant", title:"记得按时吃饭",data:"9月30日"}
}

服务端数据适配

现在主流的方案是用node.js写中间层-BFF层(Backend for Frontend),获取到后台返回的数据后进行处理, 处理为前端想要的数据,这样也不失为一种适配器模式。

与外观模式的区别:

相比于外观模式,适配器模式要了解适配对象的内部结构。

代理模式 Proxy

概念:由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介的作用。

现实生活中比如我们租房子时回去自如,自如是房屋中介机构,也就是我们的代理。

javaScript中使用最多的就是虚拟代理和缓存代理。

虚拟代理

图片loading预加载

// 创建本体对象,生成img标签,对外提供setSrc接口
var myImage = (function() {var imgNode = document.createElement('img');document.body.appendChild(imgNode);return {setSrc: function (src) {imgNode.src = src;}}
})();// 引入代理对象,图片加载之前会有个loading.gifvar proxyImage = (function () {var img = new Image();img.onload = function () {myImage.setSrc(this.src);}return {setSrc: function (src) {myImage.setSrc('loading.gif');img.src= src;}}})();// 通过代理得到图片proxyImg.setSrc('http://...');

缓存代理

可以为一些开销很大的运算结果提供暂时的储存,若下次传递的参数一致则直接返回之前的结果,大大提高效率和节省开销。

var computedFn = function () {console.log('开始');var a = 1;for (var i = 0; l = arguments.length; i < l; i++) {a = a * arguments[i];}return a;
}var proxyComputed = (function() {var cache = {};return function() {var args = Array.prototype.join.call(arguments, ',');if (args in cache) {return cache[args];}return cache[args] = computedFn.apply(this, arguments);}
})();proxyComputed(1, 2, 3, 4); // 24
proxyComputed(1, 2, 3, 4); // 24

如上所示,代理模式可以解决系统之间的耦合度以及系统资源开销大的问题。

ES6中的Proxy

ES6所提供的Proxy构造函数能够让我们轻松的使用代理模式。

// target所要代理的对象
// handler设置对所代理的对象的行为
var proxy = new Proxy(target, handler);

Vue3.0中的Proxy

vue3.0中的双向数据绑定原理用了es6中的Proxy,并优雅的解决了Proxy细节上的一些问题,从而完美的实现双向绑定,大家可以去阅读源码,这里不作展开。

装饰者模式 Decorator

概念:在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法)使原有对象可以满足用户的更复杂需求。

TypeScript的装饰器@

装饰器是一项实验性特性,在未来的版本中可能会发生改变。

具体使用方法请移步

官方文档

https://www.tslang.cn/docs/handbook/decorators.html

假设我们在开发中有这样一个需求,在不影响原有事件的前提下,新增事件实现业务,就可以通过装饰者来实现。

// 装饰者
var decorator = function (input, fn) {// 获取事件源var input = document.getElementById(input);// 若事件源已经绑定事件if (typeof input.onclick === 'function') {// 缓存事件源原有回调函数var oldClickFn = input.onclick;// 为事件源定义新的事件input.onclick = function () {// 事件源原有回调函数oldClickFn();// 执行事件源新增回调函数fn();}} else {// 事件源未绑定事件,直接为事件源添加新增回调函数input.onclick = fn;}
}

与适配者模式的区别:

在适配者模式中要了解原有方法实现的具体细节,而在装饰者模式只有当我们调用方法时才会知道其内部细节,这是对原有功能完整性的一种保护。

桥接模式 Bridge

概念:在系统沿着多个维度变化的同时,又不增加其复杂度并已达到解耦。

var spans = document.getElementsByTagName('span');
// 为用户名绑定特效
spans[0].onmouseover = function () {this.style.color = 'red';this.style.background = '#ddd';
}
spans[0].onmouseout = function () {this.style.color = '#333';this.style.background = '#f5f5f5';
}
// 为等级绑定特效
spans[1].onmouseover = function () {this.getELementsByTagName('strong')[0].style.color = 'red';this.getElementsByTagName('strong')[0].style.background = '#ddd';
}
spans[1].onmouseout = function () {this.getELementsByTagName('strong')[0].style.color = '#333';this.getElementsByTagName('strong')[0].style.background = '#f5f5f5';
}
// 提取共同点,解除与事件中的this的耦合
function changeColor (dom, color, bg) {dom.style.color = color;dom.style.background = bg;
}// 使用匿名函数,事件与业务逻辑之间的桥梁
var spans = document.getElementsByTagName('span');
spans[0].onmouseover = function () {changeColor(this, 'red', '#ddd';)
}// changeColor方法中的dom实质上是事件回调函数中的this,解除它们之间的耦合
// 我们使用了一个桥接方法-匿名回调函数。通过这个匿名回调函数
// 我们将获取到的this传递到changeColor函数中,即可实现需求
spans[0].onmouseout = function () {changeColor(this, '#333', '#f5f5f5');
}
// 同样的道理应用在用户等级上
spans[1].onmouseover = function () {changeColor(this.getElementsByTagName('strong')[0], 'red', '#ddd');
}
spans[1].onmouseout = function () {changeColor(this.getElementsByTagName('strong')[0], '#333', '#f5f5f5');
}
// 如果需求再有变化,只需要修改changeColor的内容就可以了
// 而不必去到每个事件回调函数中去修改,以新增一个桥接函数为代价

将实现层(如元素绑定的事件)与抽象层(如修饰页面UI逻辑)解耦分离,使两部分可以独立变化。

注意,有些时候,对于桥梁的添加,会造成开发成本增加,性能上也会受影响。

组合模式 Composite

概念:又称部分-整体模式,将对象组合成树形结构以表示“部分整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性

  • 中餐厅:套餐服务

  • webpack构建项目的目录结构
  • 公司部门组织架构

组合模式能够给我们提供一个清晰的组成结构。组合对象类通过继承同一个父类使其具有统一的方法,这样也方便了我们统一管理和使用。

jQuery中addClass实现

// jQuery 3.4.1
addClass: function( value ) {var classes, elem, cur, curValue, clazz, j, finalValue,i = 0;if ( isFunction( value ) ) {return this.each( function( j ) {jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );} );}classes = classesToArray( value );if ( classes.length ) {while ( ( elem = this[ i++ ] ) ) {curValue = getClass( elem );cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );if ( cur ) {j = 0;while ( ( clazz = classes[ j++ ] ) ) {if ( cur.indexOf( " " + clazz + " " ) < 0 ) {cur += clazz + " ";}}// Only assign if different to avoid unneeded rendering.finalValue = stripAndCollapse( cur );if ( curValue !== finalValue ) {elem.setAttribute( "class", finalValue );}}}}return this;
}

我们无须知道addClass的内部结构和实现细节,就可以使用addClass来对标签添加类。

享元模式 Flyweight

概念:运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销。

核心思想:共享细粒度对象

最终目标:尽量减少共享对象的数量

现实生活中,LOL、农药段位之分是享元模式的思想,咖啡厅的咖啡种类也是享元模式的思想。

在我们熟知的原型链继承中,当子类实例很多的时候,子类可以通过原型来复用父类的方法和属性来优化内存,这也是享元模式的思想。

除此之外,Node.js中的线程池、数据库的连接池、HTTP连接池以及字符常量池都是享元模式或其升级版。

参考:

《JavaScript设计模式》张容铭

微信搜索【前端食堂】你的前端食堂,记得按时吃饭。

本文已收录在前端食堂 Github https://github.com/Geekhyt/front-end-canteen,感谢Star。

修炼内功之JavaScript设计模式(二)相关推荐

  1. 【设计模式】| 修炼内功 | 23种设计模式——单例模式

    设计模式如同织锦之艺术,精心构筑,展示优美. 学习设计模式,犹如追逐清晨的曙光,扉页掀开了人生的新篇章.当你学会设计模式的奥秘,就如同走进了灯火通明的城市,丰富多彩的建筑,让你大开眼界,拥有了整个世界 ...

  2. 【设计模式】| 修炼内功 | 23种设计模式——工厂方法模式(含抽象)

    设计模式如同织锦之艺术,精心构筑,展示优美. 学习设计模式,犹如追逐清晨的曙光,扉页掀开了人生的新篇章.当你学会设计模式的奥秘,就如同走进了灯火通明的城市,丰富多彩的建筑,让你大开眼界,拥有了整个世界 ...

  3. javaScript设计模式之面向对象编程(object-oriented programming,OOP)(二)

    接上一篇 面向对象编程的理解? 答:面向对象编程,就是将你的需求抽象成一个对象,然后针对这个对象分析其特征(属性)与动作(方法).这个对象我们称之为类.面向对象编程思想其中一个特点就是封装,就是把你需 ...

  4. 《JavaScript设计模式》读书笔记(二)

    <JavaScript设计模式>----张荣铭(二) 首先说一下什么是设计模式?以及我们为什么要学习设计模式? 设计模式的定义是:设计模式是在面向对象软件设计过程中针对特定问题的简洁而优雅 ...

  5. JavaScript 设计模式基础(二)

    JavaScript 设计模式基础(一) 原型模式 在以类为中心的面向对象编程语言中,类和对象的关系就像铸模和铸件的关系,对象总是从类中创建.而原型编程中,类不是必须的,对象未必从类中创建而来,可以拷 ...

  6. javascript设计模式实践之模板方法--具有百叶窗切换图片效果的JQuery插件(二)...

    在上一篇<javascript设计模式实践之迭代器--具有百叶窗切换图片效果的JQuery插件(一)>里,通过采用迭代器模式完成了各初始化函数的定义和调用. 接下来就要完成各个切换效果的编 ...

  7. javascript 设计模式_用英雄联盟的方式讲解JavaScript设计模式(二)

    前言 大家好,这是第三篇作者对于设计模式的分享了,前两篇可以参考: 手写一下JavaScript的几种设计模式 (工厂模式,单例模式,适配器模式,装饰者模式,建造者模式) 用英雄联盟的方式讲解Java ...

  8. 16种JavaScript设计模式(中)

    简介 上文中介绍了学习设计模式前需要了解的一些基础概念和js的基础模式-原型模式,没看过的同学可以点这里,本章将介绍以下几种模式 单例模式 策略模式 代理模式 迭代器模式 发布订阅模式 命令模式 组合 ...

  9. JavaScript设计模式与开发实践 | 02 - this、call和apply

    this JavaScript的this总是指向一个对象,至于指向哪个对象,是在运行时基于函数的执行环境的动态绑定的,而非函数被声明时的环境. this的指向 this的指向大致可以分为以下4类: 作 ...

最新文章

  1. python中使用flask实现人脸实时检测
  2. UA MATH567 高维统计III 随机矩阵6 亚高斯矩阵的范数
  3. HDU 2897 邂逅明下(简单博弈)
  4. go语言学习笔记(2)命令源码文件
  5. 利用记事本创建一个ASP.NET Core RC2 MVC应用
  6. 为回馈广大学员,智捷课堂买关老师Cocos2d-x课程送Cocos2d-x图书
  7. math.trunc_带有Python示例的math.trunc()方法
  8. 作者:洪文兴(1980-),男,厦门大学自动化系副教授,厦门信息产业与信息化研究院执行院长。...
  9. 使用Postman做API自动化测试
  10. 有关 !DOCTYPE HTML
  11. 拓端tecdat|python虎扑社区论坛数据爬虫分析报告
  12. AC日记——找最大数序列 openjudge 1.9 10
  13. 中国ai人工智能发展太快_新的AI计算遥远行星的速度快100,000倍
  14. 离散数学与计算机的发展,计算机学科发展中离散数学的作用与运用
  15. 第六章 Dubbo的源码解析
  16. 手摸手。完成一个H5 抽奖功能
  17. SpringBoot项目 四种读取properties文件的方式
  18. 装完系统还要装什么_一键重装系统后需要干嘛
  19. 计算机工程学院新生欢迎标语,欢迎新生标语(精选50句)
  20. 网络安全(数据库等)

热门文章

  1. ObjectOutputStream and ObjectInputStream 序列化 transient
  2. 2014 android全球用户,2014年全球Android手机出货量占比高达81.5%
  3. 认识设备树(二)——设备树文件的格式
  4. js数据结构与算法 图的BFS和DFS
  5. CentOS7 Systemtap 安装
  6. 双数组trie树详解
  7. char占用几个字节(Byte)?
  8. Kathy老师讲述的有趣科学历史
  9. Scala隐式转换详解
  10. 图灵云服务器,别和一种语言厮守终生:为工作正确选择编程语言