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 ...
随机推荐
- java 日志技术汇总(log4j , Commons-logging,.....)
前言 在Tomcat 与weblogic 中的 日志(log4j) 配置系列一 在系列一 中, 有一个问题一直没有解决,就是部署到weblogic 中应用程序如何通过log4j写日志到文件中? 这里仅 ...
- input输入框默认文字,点击消失
<input type="text" value="请输入用户名" onfocus="if(value=='请输入用户名') {value='' ...
- AWS s3 python sdk code examples
Yet another easy-to-understand, easy-to-use aws s3 python sdk code examples. github地址:https://github ...
- redis远程连接超时
今天在用jedis连接远程的redis时,抛出连接超时异常:connect timed out. 详细信息例如以下: Exception in thread "main" r ...
- hibernate笔记加强版
hibernate 一. hibernate介绍 hibernate事实上就是ormapping框架,此框架的作用就是简单话数据库的操作. hibernate就是将用户提交的代码.參照持久化类配置文件 ...
- sendStickyBroadcast和sendStickyOrderedBroadcast
sendStickyBroadcast和sendStickyOrderedBroadcast发出的广播会一直滞留(等待),以便有人注册这则广播消息后能尽快的收到这条广播.其他功能与sendBroadc ...
- 深入理解Linux操作系统守护进程的意义
Linux服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户.提供这些服务的程序是由运行在后台的守护进程(daemons)来执行的.守护进程 ...
- 认识CoreData-基础使用
第一篇文章中并没有讲CoreData的具体用法,只是对CoreData做了一个详细的介绍,算是一个开始和总结吧. 这篇文章中会主要讲CoreData的基础使用,以及在使用中需要注意的一些细节.因为文章 ...
- PHP克隆魔术方法
克隆对象 __clone() $p2=clone $p; $p=>say(); 克隆对象的时候自动调用的方法 作用和构造方法一样是对新克隆的对象进行初始化 在这个方法中$this是副本所以可以给 ...
- PHPinstanceof filal这几个关键字的使用
instanceof表示属不属于的意思 eg: class person{} class Student extends person{} $s=new person(); $st=new stude ...