修炼内功之JavaScript设计模式(二)

发表于 3年以前  | 总阅读数:353 次

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

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

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

咳咳,天凉了请喝鸡汤~

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

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

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

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

1 外观模式 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');
})

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

2 适配器模式 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),获取到后台返回的数据后进行处理, 处理为前端想要的数据,这样也不失为一种适配器模式。

与外观模式的区别:

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

3 代理模式 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.gif
 var 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细节上的一些问题,从而完美的实现双向绑定,大家可以去阅读源码,这里不作展开。

4 装饰者模式 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;
  }
}

与适配者模式的区别:

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

5 桥接模式 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逻辑)解耦分离,使两部分可以独立变化。

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

6 组合模式 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来对标签添加类。

7 享元模式 Flyweight

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

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

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

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

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

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

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/XfDgUmj1Uc7oKHZThBhvRg

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Java 中验证时间格式的 4 种方法 2年以前  |  3870次阅读
Java经典面试题答案解析(1-80题) 4年以前  |  3651次阅读
CentOS 配置java应用开机自动启动 4年以前  |  2797次阅读
IDEA依赖冲突分析神器—Maven Helper 4年以前  |  2772次阅读
SpringBoot 控制并发登录的人数教程 4年以前  |  2452次阅读
 目录