题外:

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

下边就根据自己学习和实践过程中对js模块化编程的理解,分享一下我的经历,希望对您有所帮助:

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

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

 /*****页面业务逻辑1***begin*****/

 //定义一个全局变量,供逻辑1中的各函数共享使用
var test = 0;
function setFlag() {
test = 1;
}
function displayFlag() {
console.log(test);
} /*****页面业务逻辑1***end*****/

其他业务处理程序脚本:

 /*
* ……………………………………
* 中间业务逻辑,篇幅很长
* ……………………………………
*/

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

 /*****页面业务逻辑2***begin*****/

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

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

 setVarable();   //逻辑2不小心修改了该值

 displayFlag();  //error:预期输出1,但是却脏读成了2

输出结果如下:

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

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

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

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

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

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

 /*****页面业务逻辑1********/
function HandleOne() {
var test = 0;
this.setFlag = function() {
test = 1;
}
this.displayFlag = function() {
console.log("这是逻辑1中的变量值:" + test);
}
//返回this对象,以访问module里定义的函数
return this;
} /*
* ……………………………………
* 中间业务逻辑,篇幅很长
* ……………………………………
*/ /*****页面业务逻辑2********/
function HandleTwo() {
var test;
this.setVarable = function() {
test = 2;
}
this.displayV = function() {
console.log("这是逻辑2中的变量值:" + test);
}
//返回this对象,以访问module里定义的函数
return this;
} var H1 = HandleOne();
var H2 = HandleTwo(); H2.setVarable(); //逻辑2修改了自己的变量 H1.displayFlag(); //逻辑1输出自己的变量 H2.displayV(); //逻辑2输出自己的变量

输出结果如下:

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

 /*****页面业务逻辑1********/
var H1 = (function() {
var test = 0;
this.setFlag = function() {
test = 1;
}
this.displayFlag = function() {
console.log("这是逻辑1中的变量值:" + test);
}
//返回this对象,以访问module里定义的函数
return this;
} ()); /*
* ……………………………………
* 中间业务逻辑,篇幅很长
* ……………………………………
*/ /*****页面业务逻辑2********/
var H2 = (function() {
var test;
this.setVarable = function() {
test = 2;
}
this.displayV = function() {
console.log("这是逻辑2中的变量值:" + test);
}
//返回this对象,以访问module里定义的函数
return this;
} ()); H2.setVarable(); //逻辑2修改了自己的变量 H1.displayFlag(); //逻辑1输出自己的变量 H2.displayV(); //逻辑2输出自己的变量

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

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

通过上述的例子我们可以总结出模块化的一般思路:

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

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

模块化的编程思路如下:

 //实体函数时的模块化思路
function Moudle() { var theResult; //do something here //这一句可有可无,有则返回最终的处理结果
return theResult;
}
//执行模块过程,有返回值时可以接收返回值
Moudle(); //匿名函数时的模块化思路
var result = (function() {
var theResult; //do something here //这一句可有可无,有则返回最终的处理结果
return theResult;
});

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

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

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

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

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

 //初始化web版扫雷游戏的命名空间(方法二:返回对象列表);
games.BombGame = (function namespace() {
//可以用局部变量或者函数做一些其他的事情 //返回命名空间中的对象列表
return {
HoleControlClass:PlayBombGame,
BombListClass:BombObjectList,
BombClass:BombObject,
ImageClass:BombImgObject
};
} ()); //初始化web版扫雷游戏的命名空间(方法二:返回类对象);
games.BombGame = (new function namespace() {
//可以用局部变量或者函数做一些其他的事情 //将对象列表赋值给对象属性
this.HoleControlClass = PlayBombGame;
this.BombListClass = BombObjectList;
this.BombClass = BombObject;
this.ImageClass = BombImgObject;
} ()); //初始化web版扫雷游戏的命名空间(方法三:匿名函数封装赋值过程);
games.BombGame = {};
games.BombGame = (new function namespace() {
//可以用局部变量或者函数做一些其他的事情 //初始化
games.BombGame.HoleControlClass = PlayBombGame;
games.BombGame.BombListClass = BombObjectList;
games.BombGame.BombClass = BombObject;
games.BombGame.ImageClass = BombImgObject;
} ());

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

 var Insert = (function() {
//判断insertAdjacentHTML的支持性,如果支持,直接返回对象。
if (document.createElement("div").insertAdjacentHTML) {
return {
before: function(e, h) { e.insertAdjacentHTML("beforebegin", h); },
after: function(e, h) { e.insertAdjacentHTML("afterend", h); },
atStart: function(e, h) { e.insertAdjacentHTML("afterbegin", h); },
atEnd: function(e, h) { e.insertAdjacentHTML("beforeend", h); }
};
}
//根据要添加的类容创建文档碎片
function fragment(html) { var elt = document.createElement("div");
var flag = document.createDocumentFragment();
elt.innerHTML = html; while (elt.firstChild) {
flag.appendChild(elt.firstChild);
}
return flag;
} var Insert = { before: function(elt, html) { elt.parentNode.insertBefore(fragment(html), elt); },
after: function(elt, html) { elt.parentNode.insertBefore(fragment(html), elt); },
atstart: function(elt, html) { elt.insertBefore(fragment(html), elt.firstChild); },
atend: function(elt, html) { elt.appendChild(fragment(html)); }
};
//将新的方法绑定到元素的原型链中,以便元素对象可以直接调用插入方法
Element.prototype.insertAdjacentHTML = function(pos, html) {
switch (pos) {
case "beforebegin": return Insert.before(this, html);
case "afterend": return Insert.after(this, html);
case "afterbegin": return Insert.atstart(this, html);
}
};
//返回对象
return Insert;
} ());

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

 var whenReady = (function() {
this.ready = false;
this.funcs = []; function handle(e) {
if (this.ready) {
return;
} if (e.type === "readystatechange" && document.readyState !== "complete") {
return;
} for (var i = 0; i < this.funcs.length; i++) {
funcs[i].call(document);
} this.ready = true;
this.funcs = null;
} if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", handle, false);
document.addEventListener("readystatechange", handle, false);
window.addEventListener("load", handle, false);
}
else {
document.attachEvent("onreadystatechange", handle);
window.attachEvent("onload", handle);
} return function whenReady(f) {
if (this.ready) {
f.call(document);
}
else {
this.funcs.push(f);
}
}
} ());

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

javascript 学习笔记之模块化编程的更多相关文章

  1. javascript 学习笔记之面向对象编程(二):继承&多态

    ~~接上篇~~上一篇实现了类的实现以及类成员变量和方法的定义,下面我们来了解下面向对象中两个最重要的特性:继承和多态. 继承 js中同样可以实现类的继承这一面向对象特性,继承父类中的所有成员(变量和属 ...

  2. javascript 学习笔记之面向对象编程(一):类的实现

    ~~想是一回事,做是一回事,写出来又是一回事~~一直以来,从事C++更多的是VC++多一些,从面向过程到面向对象的转变,让我对OO的编程思想有些偏爱,将一个客观存在的规律抽象出来总是让人比较兴奋,通过 ...

  3. JavaScript:学习笔记(9)——Promise对象

    JavaScript:学习笔记(9)——Promise对象 引入Promise Primose是异步编程的一种解决方案,比传统的解决方案回调函数和事件更加合理和强大.如下面为基于回调函数的Ajax操作 ...

  4. JavaScript:学习笔记(10)——XMLHttpRequest对象

    JavaScript:学习笔记(10)——XMLHttpRequest对象 XHR对象 使用XMLHttpRequest (XHR)对象可以与服务器交互.您可以从URL获取数据,而无需让整个的页面刷新 ...

  5. 孙鑫VC学习笔记:多线程编程

    孙鑫VC学习笔记:多线程编程 SkySeraph Dec 11st 2010  HQU Email:zgzhaobo@gmail.com    QQ:452728574 Latest Modified ...

  6. Hadoop学习笔记(7) ——高级编程

    Hadoop学习笔记(7) ——高级编程 从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成 ...

  7. Java程序猿的JavaScript学习笔记(汇总文件夹)

    最终完结了,历时半个月. 内容包含: JavaScript面向对象特性分析,JavaScript高手必经之路. jQuery源代码级解析. jQuery EasyUI源代码级解析. Java程序猿的J ...

  8. Java程序猿的JavaScript学习笔记(8——jQuery选择器)

    计划按例如以下顺序完毕这篇笔记: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript ...

  9. Java程序猿JavaScript学习笔记(2——复制和继承财产)

    计划和完成在这个例子中,音符的以下序列: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaSc ...

随机推荐

  1. Android学习之 博客专栏 与 资料

    android | Android Developers Android学习系列 - 谦虚的天下 - 博客园 android基础 - 生如夏花之灿烂 - 博客园 Android开发 - 皓月繁星 - ...

  2. Mysql常见问题及优化

    本文将就以下三个问题进行展开: 1.库表设计 2.慢 SQL 问题 3.误操作.程序 bug 时怎么办 一.库表设计 1.1 引擎选择 在 mysql 5.1 中,引入了新的插件式存储引擎体系结构,允 ...

  3. linux下查询域名或IP注册信息的操作记录(whois)

    在运维工作中,有时需要查询某些域名的注册信息(域名的NS,注册用户,注册邮箱等),可以使用whois这个命令.whois命令令用来查找并显示指定帐号(或域名)的用户相关信息,因为它是到Network ...

  4. tcp_tw_reuse 与 net.ipv4.tcp_tw_recycle

    最近发现几个监控用的脚本在连接监控数据库的时候偶尔会连不上,报错: Couldn't connect to host:3306/tcp: IO::Socket::INET: connect: Cann ...

  5. 标准I/O库之打开和关闭流

    下列三个函数打开一个标准I/O流. #include <stdio.h> FILE *fopen( const char *restrict pathname, const char *r ...

  6. cocos js响应过程

    使用ccbi: js加载ccbi时候,会调用CCBReader的函数readNodeGraphFromData,从根节点递归解析子节点,使用readNodeGraph函数解析单个节点. 当碰到CCMe ...

  7. 可拖拽重排的CollectionView

    来源:wazrx 链接:http://www.jianshu.com/p/8f0153ce17f9 写在前面 这段时间都在忙新项目的事儿,没有时间倒腾,这两天闲下来,想着一直没有细细的研究Collec ...

  8. Linux--------------安装mysql(2)

    在 CentOS7 上安装 MySQL5.7 1 通过 SecureCRT 连接到阿里云 CentOS7 服务器: 2 进入到目录 /usr/local/ 中:cd /usr/local/ 3 创建目 ...

  9. Java基础知识强化之IO流笔记43:IO流练习之 复制文本文件的 5 种方式案例

     1. 案例分析: 分析: 复制数据,如果我们知道用记事本打开并能够读懂,就用字符流,否则用字节流. 通过该原理,我们知道我们应该采用字符流更方便一些. 而字符流有5种方式,所以做这个题目我们有5种方 ...

  10. Android(java)学习笔记156:Java虚拟机和Dalvik虚拟机的区别

    Google于2007年底正式发布了Android SDK, 作为 Android系统的重要特性,Dalvik虚拟机也第一次进入了人们的视野.它对内存的高效使用,和在低速CPU上表现出的高性能,确实令 ...