JavaScript 设想形式读书笔记(四)——单体形式和链式挪用

单体情势

在多种Javascript设想情势中,单体情势是最简朴,也是最基础的设想情势。它基础到好像不太像是一种设想情势,因为我们在编写代码的历程当中随时都邑用到,并不须要过量思索,这是它简朴的一面。同时,它不仅能够零丁存在,以至也能够成为其他较高等设想情势的组成部份,这也是为何说它基础的缘由。

基础构造

既然说了单体情势是异常简朴的,它的构造也是很简朴的。最简朴的单体构造实际上就是一个对象字面量:

var Singleton = {
  attribute1: true,
  attribute2: 1,

  method1: function() {
    ...
  },
  method2: function() {
    ...
  }
}

这就是一个基础的单体构造了。

然则,不是任何对象字面量都能够被称作为单体构造的,单体构造应该是一个只能被实例化一次,而且能够经由历程一个接见点接见的类。所谓接见点,能够理解为一个变量,这个变量在全局范围内能够接见到,而且只要一个。

单体构造的作用

那末单体构造的作用是什么呢,岂非只是用来建立一个实例化的对象这么简朴吗?

定名空间
固然不是的,单体最不言而喻的作用就是分别定名空间。单体构造在页面中有一个接见点,那末单体中保留的一切属性和要领也就能够从这个接见点接见了,经由历程点运算符的情势。而且也只要经由历程接见点才够接见到。Javascript中的一切变量都是能够被改写的,当一个程序员庇护多个变量的时刻,假如不将他们归类到定名空间中去的话,一旦变量被修正,查找起来将异常贫苦。同时,一个定名优越的定名空间称号也能够提示其他的程序员不要随便修正个中的变量。

var Classicemi = {
  setName: function(name) {
    ...
  },
  // 其他要领
}

在其他处所接见setName要领的时刻,肯定要经由历程Classicemi.setName才接见的到,这能够提示其他程序员这个要领的作用和声明的所在。经由历程定名空间将相似的要领组合到一同也能够增添代码的文档性。另一方面,网页上的Javascript代码会依据其用处有差别的分别,分差别的人来庇护。比方JS库代码,广告代码等。为了防备彼此之间发生争执,在全局对象中也能够给差别用处的代码分别各自的定名空间,也就是存到各个单体中。

var Classicemi = {};

Classicemi.Common = {
  ...
};

Classicemi.ErrorCodes = {
  ...
};

网页专用代码包装器
这是单体罕见用法的一个示例。
在一个网站中,有些Javascript代码是全部网站都要用到的,比方框架,库等。而有些代码是特定的网页才会用到,比方对一个页面中的DOM元素增加事宜监听等。平常我们会经由历程给页面的load事宜建立一个init要领来对一切须要的操纵举行初始化,将一切的初始化代码放在一个要领中。
比方含有一个表单的页面,我们要作废submit的默许行动并增加ajax交互。

Classicemi.RegPage = {
  FORM_ID: 'reg-form',
  OUTPUT_ID: 'reg-results',

  // 表单处置惩罚要领
  handleSubmit: function(e) {
    e.preventDefult();
    ...
  } ,
  sendRegistration: function(data) {
    ... // 发送XHR要求
  },
  ...

  // 初始化要领
  init: function() {
    Classicemi.RegPage.formEl = $(Classicemi.RegPage.FORM_ID);
    Classicemi.RegPage.outputEl = $(Classicemi.RegPage.OUTPUT_ID);

    addEvent(Classicemi.RegPage.FormEl, 'submit', Classicemi.RegPage.handleSubmit); // 增加事宜
  }
};

// 页面加载后运转初始化要领
addLoadEvent(Classicemi.PageName.init);

如许处置惩罚以后,关于不支撑XHR的老式浏览器,能够根据原有体式格局发送表单数据并革新页面。而当代浏览器中则能够阻挠表单提交的默许行动,改由ajax对页面举行部份革新,供应更好的用户体验。

在单体中示意私用成员

对象中有时刻有些属性和要领是须要举行庇护,防备被修正的,这些成员称为私用成员。在单体中声明私用成员也是庇护变量的一个好要领,别的,单体中建立私用成员的另一个长处在于因为单体只会被实例化一次,定义私用成员的时刻就不必过量斟酌内存糟蹋的题目。

伪私用成员(下划线示意法)
经由历程特别定名的变量来提示其他开辟者不要直接接见对象成员的要领。

Classicemi.Singleton = {
  // 私用成员
  _privateMethod: function() {
    ...
  },

  // 公然成员
  publicMethod: function() {
    ...
  }
}

在该单体的要领中,能够经由历程this接见其他要领,但这会有肯定的风险,因为在特别状况下this不肯定指向该单体。因而照样将挪用称号写满是最平安的做法。

运用闭包
加下划线的要领毕竟是假的,运用闭包才建立真正意义上的私用成员。我们晓得Javascript只存在函数作用域,因而要应用闭包的特征就不能运用对象字面量的情势,而要经由历程构造函数返返来完成单体对象的建立了。第一步,我们经由历程一个构造函数返回一个空对象,这就是单体对象的初始化:

var Classicemi.Singleton = (function() {
  return {};
})();

我们经由历程一个自实行构造函数返回单体对象的实例,下面就能够在这个构造函数中增加我们须要的私用对象了。

var Classicemi.Singleton = (function() {
  // 私用属性
  var privateAttribute = true;

  // 私用要领
  function privateMethod() {
    ...
  }
  return {};
})();

能够公然接见的公然属性和要领能够写在构造函数返回的对象中:

var Classicemi.Singleton = (function() {
  // 私用属性
  var privateAttribute = true;

  // 私用要领
  function privateMethod() {
    ...
  }
  return {
    publicAttribute: false,

    publicMethod: function() {
      ...  
    }
  };
})();

这就是用闭包建立私有成员的要领,这类单体情势又被成为模块情势(Module Pattern),我们建立的单体能够作为模块,对代码举行构造,并分别定名空间。

和之前说到的下划线示意私用成员要领比较起来,最大的长处就是能够建立真正的私用成员,使其不会在构造函数以外被随便修正。同时,因为单体只会被实例化一次,不必忧郁内存糟蹋的题目。单体情势是Javascript中最简朴,最盛行的情势之一。

惰性实例化单体

单体平常会在页面加载历程当中举行实例化,假如单体的体积比较大的话,能够会对加载速率形成影响。关于体积比较大,在页面加载时也临时不会起作用的单体,我们能够经由历程惰性加载(lazy loading)的体式格局举行实例化,也就是在须要的时刻再举行实例化。

要完成惰性加载,我们要借助一个静态要领来完成。在单体的定名空间中,我们声明如许一个要领getInstance()。这个要领会对单体是不是已举行了实例化举行检测,假如还没有实例,则会建立并返回实例。假如已实例化过了,则会返回现有实例。

完成惰性加载,我们要把原单体构造函数中的一切成员转移到一个内部的新构造函数中去:

Classicemi.Singleton = (function() {
  function constructor() {
    // 私用属性
    var privateAttribute = true;

    // 私用要领
    function privateMethod() {
      ...
    }
    return {
      publicAttribute: false,

      publicMethod: function() {
        ...  
      }
    };
  }
})();

这个内嵌构造函数不能从闭包外部接见,那末在闭包内部返回对象中的getInstance要领能够有接见constructor要领的特权,能够保证constructor要领只会被我们掌握。

getInstance()要领内部,起首要对单体是不是已实例化举行检查,假如已实例化过,就将其返回。假如没有实例化,就挪用constructor要领。我们须要一个变量来保留实例化后的单体。

Classicemi.Singleton = (function() {
  var uniqueInstance; // 保留实例化后的单体

  function constructor() {
    ...
  }

  return {
    getInstance: function() {
      if (!uniqueInstance) {
        uniqueInstance = constructor();
      }
      return uniqueInstance;
    }
  }
})();

单体的构造函数像如许被改写后,挪用其要领的代码就要由如许:

Classicemi.Singleton.publicMethod();

改写为:

Classicemi.Singleton.getInstance().publicMethod();

惰性加载的运用能够防备不必要的单体在页面加载时实例化影响加载速率,但引入一个getInstance()要领也会在肯定程度上增添代码的复杂性,因而惰性加载应该在必要的时刻再运用。

分支

分支(branching)手艺的意义在于依据差别的前提,对单体举行差别的实例化历程。

                    constructor
                         │condition
            ┌──────────────┼─────────────┐
            │            │            │
return   branch1      branch2      branch3

在构造函数中存在差别的实例对象,针对condition推断前提的差别返回值,构造函数返回差别的对象作为单体的实例。比方对差别的浏览器来讲,支撑的XHR对象不一样,大多数浏览器中是XMLHttpRequest的实例,初期的IE浏览器中是某种ActiveX的实例。我们在建立XHR对象的时刻,能够依据差别浏览器的支撑状况返回差别的实例,like this:

var XHRFactory = (function() {
  var standard = {
    createXHR: function() {
      return new XMLHttpRequest();
    }
  };
  var activeX = {
    createXHR: function() {
      return new ActiveXObject('Msxml2.XMLHTTP');
    }
  };
  var activeOld = {
    createXHR: function() {
      return new ActiveXObject('Microsofe.XMLHTTP');
    }
  }

  var testObj;
  try {
    testObj = standard.createXHR();
    return standard;
  } catch (e) {
    try {
      testObj = activeX.createXHR();
      return standard;
    } catch (e) {
      try {
        testObj = activeOld.createXHR();
        return standard;
      } catch (e) {
        throw new Error('No XHR object found in this environment.');
      }
    }
  }
})();

经由历程try-catch语句对浏览器XHR的支撑性举行测试同时防备抛出毛病,如许差别浏览器都能建立出自身支撑的XHR对象的实例。

单体情势之利害

单体情势之利

  1. 单体情势能很好的构造代码,因为单体对象只会实例化一次,单体对象中的代码能够方便地举行庇护。
  2. 单体情势能够天生自身的定名空间,防备自身的代码被他人随便修正。
  3. 惰性实例化,有助于机能的提拔。
  4. 分支,针对特定环境定制专属的要领。

单体情势之弊

类之间的耦合机能够加强,因为要经由历程定名空间去对一些要领举行接见,强耦合的效果会不利于单元测试。

链式挪用

提及链式挪用,绝大多数的前端开辟者肯定会立时想到赫赫有名的jQuery,这说明jQuery对开辟者头脑的约束还真是深啊。。。

Anyway,jQuery的链式挪用特征确实是给开辟带来了许多的方便,一条语句能够完成几条语句的事情。那末链式挪用是怎样完成的呢?

要完成链式挪用实际上是应用JavaScript的一些语法特征,重要分为两个部份:
1. 建立包括须要操纵的HTML元素的对象。
2. 对这个HTML元素举行操纵的要领。

将一切的要领都定义在构造器函数prototype属性所指的对象中,如许一切的实例都能够挪用这些要领,而且一切的要领都返回挪用它们的实例的援用。如许就完成了一个基础的链式挪用。

(function() {
  function _$(els) {
    this.elements = [];
    ... // 经由历程一系列操纵将婚配元素存入this.elements
  }

  window.$ = function() { // 对外接口
    return new _$(arguments);
  }
})();

接下来就能够在构造器函数的原型所指对象中增加我们须要的要领了,我们能够依据须要增加DOM要领,ajax要领等,然后就能够完成一个小JS库了~

(function() {
  function _$(els) {
    ...
  }

  _$.prototype = {
    each: function(fn) {
      for (var i = 0, len = this.length; i < len; i++) {
        fn.call(this, this.elements[i]);
      }
      return this;
    }
    ...
  }
})();

症结的一点就是每一个要领的末了都是return this;,它返回挪用要领的实例援用,如许我们能够继承让这个this去挪用其他要领,从而完成链式挪用。

运用回调
回调的情势假如根据通例的体式格局运用在一些取值器要领上的时刻,能够会给运用者形成一些贫苦。因为运用取值器的时刻,能够下一步我们须要对取到的值举行一些操纵,而链式挪用返回的是对象自身。

为了坚持链式挪用能运用,return this;是不能动的,那末要对取到的值举行操纵的话,就应该在取值器内部举行,将我们须要的操纵历程封装成函数传入取值器,将值作为自定义函数的参数,这就是典范的回调函数头脑。

(function() {
  function _$(els) {
    ...
  }

  _$.prototype = {
    getValue: function(callback) {
      callback.call(this, this.value); // 经由历程传入回调函数对取到的值举行操纵
      return this; // 同时不影响继承链式挪用
    }
    ...
  }
})();
    原文作者:classicemi
    原文地址: https://segmentfault.com/a/1190000000490447
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞