javascript 学习笔记之模块化编程
题外:
进行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 学习笔记之模块化编程的更多相关文章
- javascript 学习笔记之面向对象编程(二):继承&多态
~~接上篇~~上一篇实现了类的实现以及类成员变量和方法的定义,下面我们来了解下面向对象中两个最重要的特性:继承和多态. 继承 js中同样可以实现类的继承这一面向对象特性,继承父类中的所有成员(变量和属 ...
- javascript 学习笔记之面向对象编程(一):类的实现
~~想是一回事,做是一回事,写出来又是一回事~~一直以来,从事C++更多的是VC++多一些,从面向过程到面向对象的转变,让我对OO的编程思想有些偏爱,将一个客观存在的规律抽象出来总是让人比较兴奋,通过 ...
- JavaScript:学习笔记(9)——Promise对象
JavaScript:学习笔记(9)——Promise对象 引入Promise Primose是异步编程的一种解决方案,比传统的解决方案回调函数和事件更加合理和强大.如下面为基于回调函数的Ajax操作 ...
- JavaScript:学习笔记(10)——XMLHttpRequest对象
JavaScript:学习笔记(10)——XMLHttpRequest对象 XHR对象 使用XMLHttpRequest (XHR)对象可以与服务器交互.您可以从URL获取数据,而无需让整个的页面刷新 ...
- 孙鑫VC学习笔记:多线程编程
孙鑫VC学习笔记:多线程编程 SkySeraph Dec 11st 2010 HQU Email:zgzhaobo@gmail.com QQ:452728574 Latest Modified ...
- Hadoop学习笔记(7) ——高级编程
Hadoop学习笔记(7) ——高级编程 从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成 ...
- Java程序猿的JavaScript学习笔记(汇总文件夹)
最终完结了,历时半个月. 内容包含: JavaScript面向对象特性分析,JavaScript高手必经之路. jQuery源代码级解析. jQuery EasyUI源代码级解析. Java程序猿的J ...
- Java程序猿的JavaScript学习笔记(8——jQuery选择器)
计划按例如以下顺序完毕这篇笔记: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript ...
- Java程序猿JavaScript学习笔记(2——复制和继承财产)
计划和完成在这个例子中,音符的以下序列: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaSc ...
随机推荐
- Android_消息机制
Android通过Looper.Handler来实现消息循环机制. Android的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环. Android系统中的Looper负责管理线程的消 ...
- nginx redis tomcat 分布式web应用 session共享
目标:多台tomcat 使用redis实现共享session.redis的安装请参阅:centos上安装redis nginx 作为目前最流行的开源反向代理HTTP Server,用于实现资源缓存.w ...
- Delphi 根据快捷方式路径取源文件地址
unit Unit1;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, ...
- Nginx入门之两种handler函数的挂载方式
请在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 接着上次的文章,今天研究<深入理解Nginx模块开发与架构解析>一书中给出的mytest的例子,发现和 /tengine.t ...
- MySQL性能监控工具-MONyog
1.登录配置界面 2.show processlist 查看当前使用的进程 3.警告建议你应该优化哪些参数. 4.介绍一下慢查询的配置,其它的可以自己配置,都是简单的英文. 该工具,用着还不错.其 ...
- careercup-位操作5.1
5.1 写程序使整数N中第i位到第j位的值与整数M中的相同. 题目 给定两个32位的数,N和M,还有两个指示位的数,i和j. 写程序使得N中第i位到第j位的值与M中的相同(即:M变成N的子串且位于N的 ...
- CCLabelTTF 如何支持换行符和换行
参考自http://www.cocos2d-x.org/wiki/How_does_CCLabelTTF_support_line_breaks_and_wrapping 环境: cocos2d-x ...
- sklearn两种保存模型的方式
作者:卢嘉颖 链接:https://www.zhihu.com/question/27187105/answer/97334347 来源:知乎 著作权归作者所有,转载请联系作者获得授权. 1. pic ...
- DevExpress GridControl 列中显示图片
一.GridControl 的Columns中添加列 1.列名:FieldName命名为img 2.类型:ColumnEdit属性中 选择PictureEdit类型(RepositoryItemPic ...
- Object-C日志记录
在Object-C中,将日志信息输出到控制台是非常简单的.实际上NSLog()函数很像C语言里面的printf()函数,出了要用一个%@符号代表一个对象. NSLog(@"The curre ...