javascript 学习笔记之模块化编程

题外:javascript

  进行web开发3年多了,javascript(后称js)用的也比较多,可是大部分都局限于函数的层次,有些公共的js函数可重用性很差,形成了程序的大量冗余,可读性差(虽然一直保留着注释的习惯,可是最后发现注释远远不够),影响了页面的加载速度和性能。去年开始着手对既有前端脚本进行重构和优化,查阅了不少技术大牛分享的资料,也比较系统的阅读了一遍《javascript权威指南》,js模块化编程深深的吸引了我,它改变了我编写js脚本程序的方式,同时也让代码的可读性和可维护性进一步加强。html

下边就根据本身学习和实践过程当中对js模块化编程的理解,分享一下个人经历,但愿对您有所帮助:前端

  你们都知道,js中的变量(variable)有其做用范围,好比:函数里用var定义的变量在函数外是看不到的,而定义在函数外面的变量(不能有没有var修饰)均是全局变量,在js程序的任何位置均可以访问。嗯,实际上咱们在工做过程当中,业务逻辑比较多,而一个业务逻辑包含多个函数,函数之间共享使用某个变量,这样问题就来了,若是另一个业务逻辑不当心定义了或者修改了这个变量,就会形成这个全局变量被污染,前一个业务逻辑就会出现脏读,过程测试以下:java

一个很长的页面脚本程序包含两个子业务处理过程1和2,业务处理程序1须要定义两个函数和一个变量,一个函数设置变量,一个函数读取输出变量,以下:web

 1 /*****页面业务逻辑1***begin*****/
 2 
 3 //定义一个全局变量,供逻辑1中的各函数共享使用
 4 var test = 0;
 5 function setFlag() {
 6     test = 1;
 7 }
 8 function displayFlag() {
 9     console.log(test);
10 }
11 
12 /*****页面业务逻辑1***end*****/

 其余业务处理程序脚本:编程

1 /*
2 *   ……………………………………
3 *    中间业务逻辑,篇幅很长
4 *   ……………………………………
5 */

 业务处理程序2开始,逻辑处理也定义了两个函数和一个变量,一个函数设置变量,一个函数读取变量进行其余处理,不幸的是,这个全局变量采用了同业务逻辑1相同的名字:app

 1 /*****页面业务逻辑2***begin*****/
 2 
 3 //定义一个全局变量,供逻辑1中的各函数共享使用
 4 var test = 0;
 5 function setVarable() {
 6   test = 1;
 7 }
 8 function displayV() {
 9     console.log(test);
10 }
11 
12 /*****页面业务逻辑2***end*****/

 程序过程在进行逻辑2后再进行逻辑1,此时出现了意外:模块化

1 setVarable();   //逻辑2不当心修改了该值
2 
3 displayFlag();  //error:预期输出1,可是却脏读成了2

 输出结果以下:函数

 

很明显,实际输出的结果并非指望的结果,此外还有另一种状况,若是某个js脚本程序被共享为一个共用的脚本块,在多个地方调用(引入)这个脚本块时,也会很容易出现这个问题。性能

而模块化编程(Module)的出现就解决了这个问题,除此以外模块化编程还有其余几个特色:

1. 维护一个干净前端脚本的变量环境,保护必定做用范围内定义的全局变量不被范围外程序的污染;

2. 前端脚本程序的可重用性大大提升,可读性和可维护性进一步加强;

3. 能够组合多个module脚本,抽象成一个公共的脚本库,提升代码的开发效率;

前面说过,函数内部定义的变量函数外看不到(即不可用),为了保护变量环境的做用域,这正是咱们须要的结果,故把整个业务处理逻辑扔到一个函数里实现就能够实现一个模块的定义,改写上面逻辑1和逻辑2的代码以下:

 1 /*****页面业务逻辑1********/
 2 function HandleOne() {
 3     var test = 0;
 4     this.setFlag = function() {
 5         test = 1;
 6     }
 7     this.displayFlag = function() {
 8     console.log("这是逻辑1中的变量值:" + test);
 9     }
10     //返回this对象,以访问module里定义的函数
11     return this;
12 }
13 
14 /*
15 *   ……………………………………
16 *    中间业务逻辑,篇幅很长
17 *   ……………………………………
18 */
19 
20 /*****页面业务逻辑2********/
21 function HandleTwo() {
22     var test;
23     this.setVarable = function() {
24         test = 2;
25     }
26     this.displayV = function() {
27         console.log("这是逻辑2中的变量值:" + test);
28     }
29     //返回this对象,以访问module里定义的函数
30     return this;
31 }
32 
33 var H1 = HandleOne();
34 var H2 = HandleTwo();
35 
36 H2.setVarable();   //逻辑2修改了本身的变量
37 
38 H1.displayFlag();  //逻辑1输出本身的变量
39 
40 H2.displayV();     //逻辑2输出本身的变量

 输出结果以下:

由上图可知,在模块化编程下,每一个模块内部使用的共用变量都很好的被保护起来了,不在收到外面其余逻辑处理的干扰,可是上述过程须要咱们定义两个函数模块,若是咱们不想额外定义任何中间变量,咱们能够采用匿名函数来从新实现上述过程,代码改写以下:

 1 /*****页面业务逻辑1********/
 2 var H1 = (function() {
 3     var test = 0;
 4     this.setFlag = function() {
 5         test = 1;
 6     }
 7     this.displayFlag = function() {
 8         console.log("这是逻辑1中的变量值:" + test);
 9     }
10     //返回this对象,以访问module里定义的函数
11     return this;
12 } ());
13 
14 /*
15 *   ……………………………………
16 *    中间业务逻辑,篇幅很长
17 *   ……………………………………
18 */
19 
20 /*****页面业务逻辑2********/
21 var H2 = (function() {
22     var test;
23     this.setVarable = function() {
24         test = 2;
25     }
26     this.displayV = function() {
27         console.log("这是逻辑2中的变量值:" + test);
28     }
29     //返回this对象,以访问module里定义的函数
30     return this;
31 } ());
32 
33 H2.setVarable();   //逻辑2修改了本身的变量
34 
35 H1.displayFlag();  //逻辑1输出本身的变量
36 
37 H2.displayV();     //逻辑2输出本身的变量

 上面的是用匿名函数实现的模块化封装,输出的结果同实体函数时同样,是否是比实体函数时更加简洁了?!

注:上述过程当中咱们在每一个模块中返回了this对象,是由于咱们须要在后续的逻辑中调用该模块中的函数,若是在实践过程当中模块处理程序不须要被外部逻辑调用,而只是在模块内部输出结果便可的话,咱们只需返回模块最终处理的结果值或者不须要返回语句,依据具体状况具体分析。

经过上述的例子咱们能够总结出模块化的通常思路:

1. 把相关联的一系列函数及变量的定义放在一个函数(匿名函数也行)中便可造成一个模块,模块中的变量和函数的做用域仅限于模块内部,外部没法直接调用,模块能够返回既定逻辑的处理结果。

2. 若是须要在模块外部提供调用模块中函数或者变量的接口,则须要将模块中函数或变量的定义用this标记,而后在模块最后返回这个this对象(函数中的this对象指的是window对象)。

模块化的编程思路以下:

 1 //实体函数时的模块化思路
 2 function Moudle() {
 3 
 4     var theResult;
 5 
 6     //do something here
 7     
 8     //这一句无关紧要,有则返回最终的处理结果
 9     return theResult;
10 }
11 //执行模块过程,有返回值时能够接收返回值
12 Moudle();
13 
14 //匿名函数时的模块化思路
15 var result = (function() {
16     var theResult;
17 
18     //do something here
19 
20     //这一句无关紧要,有则返回最终的处理结果
21     return theResult;
22 });

另:大部分web开发的后台语言都采用C#或者java,熟悉这两种语言的童鞋都知道,它们内部封装了不少函数库(包),C#中要用using引入,java中要用import引入,这些库或者包都是把一系列相关联的函数、变量、类等对象封装到一个命名空间中,方便后续调用的更加方便、清晰,javascript也能够实现这种命名空间式的封装,拿以前的web版扫雷小游戏为例,游戏中定义了四个类(即四个模块,模块总体做为一个对象,可根据需求扩展更多):PlayBombGame、BombObjectList、BombObject、BombImgObject,咱们能够把这四个模块对象封装到一个叫games.BombGame的命名空间中,代码以下:

1 //初始化外层命名空间
2 var games;
3 if (!games) games = {};
4 //初始化web版扫雷游戏的命名空间(方法一);
5 games.BombGame = {};
6 games.BombGame.HoleControlClass = PlayBombGame;
7 games.BombGame.BombListClass = BombObjectList;
8 games.BombGame.BombClass = BombObject;
9 games.BombGame.ImageClass = BombImgObject;

 调用游戏接口初始化时以下:

1 var GameObj = new games.BombGame.HoleControlClass("Timer", "BombNum", "ContentSection", "TryAgain", 16, 30, 99);
2 GameObj.play();

固然,子命名空间games.BombGame的初始化还有其余几种方法,代码以下:

 1 //初始化web版扫雷游戏的命名空间(方法二:返回对象列表);
 2 games.BombGame = (function namespace() {
 3     //能够用局部变量或者函数作一些其余的事情
 4 
 5     //返回命名空间中的对象列表
 6     return {
 7         HoleControlClass:PlayBombGame,
 8         BombListClass:BombObjectList,
 9         BombClass:BombObject,
10         ImageClass:BombImgObject
11     };
12 } ());
13 
14 //初始化web版扫雷游戏的命名空间(方法二:返回类对象);
15 games.BombGame = (new function namespace() {
16     //能够用局部变量或者函数作一些其余的事情
17 
18     //将对象列表赋值给对象属性
19     this.HoleControlClass = PlayBombGame;
20     this.BombListClass = BombObjectList;
21     this.BombClass = BombObject;
22     this.ImageClass = BombImgObject;
23 } ());
24 
25 //初始化web版扫雷游戏的命名空间(方法三:匿名函数封装赋值过程);
26 games.BombGame = {};
27 games.BombGame = (new function namespace() {
28     //能够用局部变量或者函数作一些其余的事情
29 
30     //初始化
31     games.BombGame.HoleControlClass = PlayBombGame;
32     games.BombGame.BombListClass = BombObjectList;
33     games.BombGame.BombClass = BombObject;
34     games.BombGame.ImageClass = BombImgObject;
35 } ());

 

模块化编程的举例(例1,文档元素指定点插入的通用方法):

 1 var Insert = (function() {  2     //判断insertAdjacentHTML的支持性,若是支持,直接返回对象。
 3     if (document.createElement("div").insertAdjacentHTML) {  4         return {  5             before: function(e, h) { e.insertAdjacentHTML("beforebegin", h); },  6             after: function(e, h) { e.insertAdjacentHTML("afterend", h); },  7             atStart: function(e, h) { e.insertAdjacentHTML("afterbegin", h); },  8             atEnd: function(e, h) { e.insertAdjacentHTML("beforeend", h); }  9  }; 10  } 11     //根据要添加的类容建立文档碎片
12     function fragment(html) { 13 
14         var elt = document.createElement("div"); 15         var flag = document.createDocumentFragment(); 16         elt.innerHTML = html; 17 
18         while (elt.firstChild) { 19  flag.appendChild(elt.firstChild); 20  } 21         return flag; 22  } 23     
24     var Insert = { 25 
26         before: function(elt, html) { elt.parentNode.insertBefore(fragment(html), elt); }, 27         after: function(elt, html) { elt.parentNode.insertBefore(fragment(html), elt); }, 28         atstart: function(elt, html) { elt.insertBefore(fragment(html), elt.firstChild); }, 29         atend: function(elt, html) { elt.appendChild(fragment(html)); } 30  }; 31     //将新的方法绑定到元素的原型链中,以便元素对象能够直接调用插入方法
32     Element.prototype.insertAdjacentHTML = function(pos, html) { 33         switch (pos) { 34             case "beforebegin": return Insert.before(this, html); 35             case "afterend": return Insert.after(this, html); 36             case "afterbegin": return Insert.atstart(this, html); 37  } 38  }; 39     //返回对象
40     return Insert; 41 } ());

(例2,文档初始化事件的通用封装):

 1 var whenReady = (function() {
 2     this.ready = false;
 3     this.funcs = [];
 4 
 5     function handle(e) {
 6         if (this.ready) {
 7             return;
 8         }
 9 
10         if (e.type === "readystatechange" && document.readyState !== "complete") {
11             return;
12         }
13 
14         for (var i = 0; i < this.funcs.length; i++) {
15             funcs[i].call(document);
16         }
17 
18         this.ready = true;
19         this.funcs = null;
20     }
21 
22     if (document.addEventListener) {
23         document.addEventListener("DOMContentLoaded", handle, false);
24         document.addEventListener("readystatechange", handle, false);
25         window.addEventListener("load", handle, false);
26     }
27     else {
28         document.attachEvent("onreadystatechange", handle);
29         window.attachEvent("onload", handle);
30     }
31 
32     return function whenReady(f) {
33         if (this.ready) {
34             f.call(document);
35         }
36         else {
37             this.funcs.push(f);
38         }
39     }
40 } ());

~~~以上是我对js模块化编程的理解,若有纰漏,还请各位技术大牛指出完善~~~

相关文章
相关标签/搜索