手柄君的小阁

个人私货聚集地

前端攻略 – JavaScript 设计模式

本文最后更新于 2018 年 1 月 31 日,其中的内容可能有所发展或发生改变,敬请注意。

文章作者: @聂微东 原文连接 (前端攻略系列(三) - javascript 设计模式(文章很长,请自备瓜子,水果和眼药水))

7C936F76A0BFDBFF778E1A2BBFE2DEC9

  一直都在考虑这个月分享大家什么东西最好,原计划是打算写一些HTML5中JS方面的内容或者是AJAX方面的,可是自己由于表达能力,时间,还有个人工作方面的问题,这个还是等下个月再做分享吧^.^。

  老规矩,开始正文以前先交代自己写这篇文章的目的和一些注意事项:

1.首先本人一直从事前端开发,所以除了JavaScript其他的语言了解不深,所以文章只会以JavaScript语言的角度去论证;

2.其实我个人在项目用过的模式也不多,对模式的概念的理解也没有那么抽象,所以最近在面试中如果面试官问到与模式相关的问题,自己感觉在对答过程中很郁闷,很多东西表达不清楚,于是就找了些相关资料,才会有这篇文章分享;

3.JavaScript模式与前端的工作和成长密不可分,因为这确实不是一个简单的话题,所以我只能尽力用简单表达和例子阐明,而且园子里有很多的高手,所以希望大家踊跃发言(由于水平有限,请大家多多指教,希望嘴下留情);

4.由于这篇文章更多的只是想起到一个介绍和讲解的作用,并不打算对每种模式进行细致的分析,所以每种模式只用到一个至二个例子,可能会造成这个例子的表达并不是最优的或者不够全面,如果各位看官觉得不过瘾,可以再去查找相关资料;

5.做任何事都需要坚持,写博客也是一样,嘿嘿,每月至少一篇(文章确实较长,希望能对朋友们有所帮助,重点部分在前言中有介绍,大家可以选择感兴趣的模式进行深入)。

6.欢迎转载,不过请注明出处,谢谢。
了解JavaScript设计模式我们需要知道的一些必要知识点:(内容相对基础,高手请跳过)

 

>>>闭包:关于闭包这个月在园子里有几篇不错的分享了,在这我也从最实际的地方出发,说说我的理解。

1.闭包最常用的方式就是返回一个内联函数(何为内联函数?就是在函数内部声明的函数);

2.在JavaScript中有作用域和执行环境的问题,在函数内部的变量在函数外部是无法访问的,在函数内部却可以得到全局变量。由于种种原因,我们有时候需要得到函数内部的变量,可是用常规方法是得不到的,这时我们就可以创建一个闭包,用来在外部访问这个变量。

3.闭包的用途 主要就是上一点提到的读取函数内部变量,还有一个作用就是可以使这些变量一直保存在内存中。

4.使用闭包要注意,由于变量被保存在内存中,所以会对内存造成消耗,所以不能滥用闭包。解决方法是 在退出函数之前,将不使用的局部变量全部删除。

最后还是上一套闭包的代码吧,这样更直观。

function f(){
    var n = 999;
    function f1(){
      alert(n+=1);
    }
    return f1;
  }
  var result = f();
  result(); // 1000
  result(); // 1001
  result(); // 1002

 

 

>封装:通过将一个方法或者属性声明为私用的,可以让对象的实现细节对其他对象保密以降低对象之间的耦合程度,可以保持数据的完整性并对其修改方式加以约束,这样可以是代码更可靠,更易于调试。封装是面向对象的设计的基石。

尽管JavaScript是一门面向对象的语言,可它并不具备将成员声明为公用或私用的任何内部机制,所以我们只能自己想办法实现这种特性。下面还是通过一套完整的代码去分析,介绍什么是私有属性和方法,什么是特权属性和方法,什么是属性和方法,什么是静态属性和方法。

  私有属性和方法:函数有作用域,在函数内用var 关键字声明的变量在外部无法访问,私有属性和方法本质就是你希望在对象外部无法访问的变量。

  特权属性和方法:创建属性和方法时使用的this关键字,因为这些方法定义在构造器的作用域中,所以它们可以访问到私有属性和方法;只有那些需要直接访问私有成员的方法才应该被设计为特权方法。

  共有属性和方法:直接链在prototype上的属性和方法,不可以访问构造器内的私有成员,可以访问特权成员,子类会继承所有的共有方法。

  共有静态属性和方法:最好的理解方式就是把它想象成一个命名空间,实际上相当于把构造器作为命名空间来使用。

/* -- 封装 -- */
  var _packaging =function(){
     //私有属性和方法
     var name ='Darren';
     var method1 =function(){
        //...
     }
     //特权属性和方法
     this.title ='JavaScript Design Patterns' ;
     this.getName =function(){
        return name;
     }
  }
  //共有静态属性和方法
  _packaging._name ='Darren code';
  _packaging.alertName =function(){
     alert(_packaging._name);
  }
  //共有属性和方法
  _packaging.prototype = {
     init:function(){
        //...
     }
  }

 

 

>>继承:继承本身就是一个抽象的话题,在JavaScript中继承更是一个复杂的话题,因为JavaScript想要实现继承有两种实现方式,分别是类式继承和原型式继承,每种实现的方式都需要采取不少措施,下面本人通过分析例子的方式讲解JavaScript中这个很重要的话题。

/* -- 类式继承 -- */
  //先声明一个超类
  function Person(name){
    this.name = name;
  }
  //给这个超类的原型对象上添加方法 getName 
  Person.prototype.getName =function(){
     returnthis.name;
  }
  //实例化这个超类
  var a =new Person('Darren1')
  alert(a.getName());
  //再声明类
  function Programmer(name,sex){
     //这个类中要调用超类Person的构造函数,并将参数name传给它
     Person.call(this,name);
     this.sex = sex;
  }
  //这个子类的原型对象等于超类的实例
  Programmer.prototype =new Person();
  //因为子类的原型对象等于超类的实例,所以
  //prototype.constructor这个方法也等于超类构造函数,
  //你可以自己测试一下,如果没这一步,
  //alert(Programmer.prototype.constructor),这个
  //是Person超类的引用,所以要从新赋值为自己本身
  Programmer.prototype.constructor = Programmer;
  //子类本身添加了getSex 方法
  Programmer.prototype.getSex =function(){
     returnthis.sex;
  }
  //实例化这个子类
  var _m =new Programmer('Darren2','male');
  //自身的方法
  alert(_m.getSex());
  //继承超类的方法
  alert(_m.getName());

代码都不难,只要对 原型链 有基础就能理解。类式继承模式是JavaScript继承主要的模式,几乎所有用面向对象方式编写的JavaScript代码中都用到了这种继承,又因为在各种流行语言中只有JavaScript使用原型式继承,因此最好还是使用类式继承。可是要熟悉JavaScript语言,原型继承也是我们必须所了解的,至于在项目中是否使用就得看个人编码风格了。

/* -- 原型式继承 -- */
  //clone()函数用来创建新的类Person对象
  var clone =function(obj){
var _f =function(){};
     //这句是原型式继承最核心的地方,函数的原型对象为对象字面量
     _f.prototype = obj; 
     returnnew _f;
  }
  //先声明一个对象字面量
  var Person = {
     name:'Darren',
     getName:function(){
        returnthis.name;
     }
  }
  //不需要定义一个Person的子类,只要执行一次克隆即可
  var Programmer = clone(Person);
  //可以直接获得Person提供的默认值,也可以添加或者修改属性和方法
  alert(Programmer.getName())
  Programmer.name ='Darren2'
  alert(Programmer.getName())
 
  //声明子类,执行一次克隆即可
  var Someone = clone(Programmer);

我是分割线,高贵而优雅


JavaScript设计模式的作用 - 提高代码的重用性,可读性,使代码更容易的维护和扩展。

 

1.单体模式,工厂模式,桥梁模式个人认为这个一个优秀前端必须掌握的模式,对抽象编程和接口编程都非常有好处。

2.装饰者模式和组合模式有很多相似的地方,它们都与所包装的对象实现同样的接口并且会把任何方法的调用传递给这些对象。装饰者模式和组合模式是本人描述的较吃力的两个模式,我个人其实也没用过,所以查了很多相关资料和文档,请大家海涵。

3.门面模式是个非常有意思的模式,几乎所有的JavaScript库都会用到这个模式,假如你有逆向思维或者逆向编程的经验,你会更容易理解这个模式(听起来有挑战,其实一接触你就知道这是个很简单的模式);还有配置器模式得和门面模式一块拿来说,这个模式对现有接口进行包装,合理运用可以很多程度上提高开发效率。这两个模式有相似的地方,所以一块理解的话相信都会很快上手的。

4.享元模式是一种以优化为目的的模式。

5.代理模式主要用于控制对象的访问,包括推迟对其创建需要耗用大量计算资源的类得实例化。

6.观察者模式用于对对象的状态进行观察,并且当它发生变化时能得到通知的方法。用于让对象对事件进行监听以便对其作出响应。观察者模式也被称为“订阅者模式”。

7.命令模式是对方法调用进行封装的方式,用命名模式可以对方法调用进行参数化和传递,然后在需要的时候再加以执行。

8.职责链模式用来消除请求的发送者和接收者之间的耦合。

JavaScript设计模式都有哪些?

单体(Singleton)模式: 绝对是JavaScript中最基本最有用的模式。

单体在JavaScript的有多种用途,它用来划分命名空间。可以减少网页中全局变量的数量(在网页中使用全局变量有风险);可以在多人开发时避免代码的冲突(使用合理的命名空间)等等。

在中小型项目或者功能中,单体可以用作命名空间把自己的代码组织在一个全局变量名下;在稍大或者复杂的功能中,单体可以用来把相关代码组织在一起以便日后好维护。

使用单体的方法就是用一个命名空间包含自己的所有代码的全局对象,示例:

ar functionGroup = {
    name:'Darren',
    method1:function(){
      //code
    },
    init:function(){
      //code
    }
  }

或者

var functionGroup  =newfunction myGroup(){
    this.name ='Darren';
    this.getName =function(){
      returnthis.name
    }
    this.method1 =function(){}
    ...
  }

 

工厂(Factory)模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。

工厂就是把成员对象的创建工作转交给一个外部对象,好处在于消除对象之间的耦合(何为耦合?就是相互影响)。通过使用工厂方法而不是new关键字及具体类,可以把所有实例化的代码都集中在一个位置,有助于创建模块化的代码,这才是工厂模式的目的和优势。

举个例子:你有一个大的功能要做,其中有一部分是要考虑扩展性的,那么这部分代码就可以考虑抽象出来,当做一个全新的对象做处理。好处就是将来扩展的时候容易维护 - 只需要操作这个对象内部方法和属性,达到了动态实现的目的。非常有名的一个示例 - XHR工厂

var XMLHttpFactory =function(){};      //这是一个简单工厂模式
  XMLHttpFactory.createXMLHttp =function(){
    var XMLHttp = null;
    if (window.XMLHttpRequest){
      XMLHttp = new XMLHttpRequest()
    }elseif (window.ActiveXObject){
      XMLHttp = new ActiveXObject("Microsoft.XMLHTTP")
    }
  return XMLHttp;
  }
  //XMLHttpFactory.createXMLHttp()这个方法根据当前环境的具体情况返回一个XHR对象。
  var AjaxHander =function(){
    var XMLHttp = XMLHttpFactory.createXMLHttp();
    ...
  }

工厂模式又区分简单工厂模式和抽象工厂模式,上面介绍的是简单工厂模式,这种模式用的更多也更简单易用。抽象工厂模式的使用方法就是 - 先设计一个抽象类,这个类不能被实例化,只能用来派生子类,最后通过对子类的扩展实现工厂方法。 示例:

var XMLHttpFactory =function(){};      //这是一个抽象工厂模式
  XMLHttpFactory.prototype = {
     //如果真的要调用这个方法会抛出一个错误,它不能被实例化,只能用来派生子类
     createFactory:function(){
        thrownew Error('This is an abstract class');
     }
  }
  //派生子类,文章开始处有基础介绍那有讲解继承的模式,不明白可以去参考原理
  var XHRHandler =function(){
     XMLHttpFactory.call(this);
  };
  XHRHandler.prototype =new XMLHttpFactory();
  XHRHandler.prototype.constructor = XHRHandler;
  //重新定义createFactory 方法
  XHRHandler.prototype.createFactory =function(){
     var XMLHttp =null;
     if (window.XMLHttpRequest){
        XMLHttp =new XMLHttpRequest()
     }elseif (window.ActiveXObject){
        XMLHttp =new ActiveXObject("Microsoft.XMLHTTP")
     }
     return XMLHttp;
  }

 桥接(bridge)模式:在实现API的时候,桥梁模式灰常有用。在所有模式中,这种模式最容易立即付诸实施。

桥梁模式可以用来弱化它与使用它的类和对象之间的耦合,就是将抽象与其实现隔离开来,以便二者独立变化;这种模式对于JavaScript中常见的时间驱动的编程有很大益处,桥梁模式最常见和实际的应用场合之一是时间监听器回调函数。先分析一个不好的示例:

element.onclick =function(){
   new setLogFunc();
};

为什么说这个示例不好,因为从这段代码中无法看出那个LogFunc方法要显示在什么地方,它有什么可配置的选项以及应该怎么去修改它。换一种说法就是,桥梁模式的要诀就是让接口“可桥梁”,实际上也就是可配置。把页面中一个个功能都想象成模块,接口可以使得模块之间的耦合降低。

掌握桥梁模式的正确使用收益的不只是你,还有那些负责维护你代码的人。把抽象于其实现隔离开,可独立地管理软件的各个部分,bug也因此更容易查找。

桥梁模式目的就是让API更加健壮,提高组件的模块化程度,促成更简洁的实现,并提高抽象的灵活性。一个好的示例:

element.onclick =function(){  //API可控制性提高了,使得这个API更加健壮
    new someFunction(element,param,callback);
}

 

 

 

  1. 头像 未闻未名说道:

    无力吐槽这个网站的前端。

    1. 头像 手柄君~说道:

      emm,我觉得我有必要找一下这个主题的作者

  2. 头像 farquer说道:

    可是我在 jQuery 中无法自拔

    1. 头像 手柄君~说道:

      原生大法好

  3. 头像 晨旭说道:

    噫→_→ c语言大法好

    1. 头像 Handle说道:

      JS保平安

  4. Pt Pt说道:

    代码在移动端有些错乱
    建议使用github gist再把js下到本地

    1. 头像 手柄君~说道:

      没有靠谱的代码显示器啊

吐槽 未闻未名 放弃治疗