高级javascript---模块化编程
随着网站逐渐变成“互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂
网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑。
Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
但是,Javascript不是一种模块化编程语言,它不支持"类"(class),更不要说"模块"(module)了。(正在制定中的ECMAScript标准第六版,将正式支持"类"和"模块",但还需要很长时间才能投入实用。)
Javascript社区做了很多努力,在现有的运行环境中,实现"模块"的效果。本文主要通过自己动手编程,实现一个简易版的require.js来讲述怎么应用模块化编程,以及模块化的原理,以便更好的理解require.js 及seajs等现有的模块化管理框架。虽然这不是初级教程,但是只要稍稍了解Javascript的基本语法,就能看懂。
一、原始写法
模块就是实现特定功能的一组方法。
只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
function module1(){
//...
}
function module2(){
//...
}
上面的函数module1()和mmodule2(),组成一个模块。使用的时候,直接调用就行了。
这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
二、对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
上面的函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。
module1.m1();
但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
module1._count = 5;
三、立即执行函数写法
使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
module1就是Javascript模块的基本写法。但是上面的例子只是演示了”模块“这个概念,而我们工作中遇到的更多的问题是如何组织这些模块。如果有过php的编程经验的话,你就会想到php中有一个require()函数(也有人说这不是函数,因为括号可以省略),当我们在编写模块A时,如果要用到之前定义过的模块B时,只要require('b.php')就可以了。但是在javascript中是没有这种现成的方法的。不过我们知道,javascript的语法是非常灵活的,只要有运用一定的技巧,就可以模拟其它编程语言中的特性,比如我们熟知的extend() 继承就是一个很好的例子。当然,现在的这个模块化require方法,也有现成的库可以使用了,比如require.js, seajs等等,但是它们大都有一个巨大的身躯,我们一下子难以看清它是怎么实现的。
下面我贴下我实现的简易版源码
/**
* 模块化编程简易框架
*/
;(function() {
//存放所有声明过的模块
var modules = {};
//存放已解析的模块
var dn = {} /**
* 判断是否为一个数组
*/
function isArray(arr){
return Object.prototype.toString.call(arr) == '[object Array]';
} /*
* 解析申请的模块
* @param {array} ids 模块id
* @param {function} callback 回调
*/
function makeRequire(ids, callback) {
var r = ids.length,
shim = [];
ids.forEach(function(name,i) {
shim[i] = build(modules[name])
})
if (callback) {
callback.apply(null,shim);
} else {
shim = null;
}
} /**
*解析依赖关系
* @param {object} module 模块对象
* @return {array} 一个由所有依赖模块所返的对象所组成的数组
*/
function parseDeps(module) {
var deps = module['deps'],
temp = [];
deps.forEach(function(id, index) {
temp.push(build(modules[id]))
})
return temp;
} /**
* 返回模块中定义的对象
* @param {object} module 定义的模块
* @return 模块返回的对象
*/
function build(module){
var exist,exports;
factory = module['factory'],
id = module['id']; exist = dn[id]; if(exist){
return exist;
} if(module['deps']){
depsList = parseDeps(module);
exports = factory.apply(module, depsList);
}else{
exports = factory() || {};
} dn[id] = exports;
return exports;
} /**
* 导入模块
* @param {id} id 模块ID
* @param {function} callback 回调函数
* @return {object}
*/
function require(id,callback){
if(isArray(id)){
return makeRequire(id,callback);
}
if (!modules[id]) {
throw "module " + id + " 模块不存在!";
} if(callback){
var module = build(modules[id]);
callback(module);
}else{
if(modules[id].factory){
return modules[id];
}
}
}
/**
* 定义一个模块
* @param {string} id 模块ID
* @param {array} deps 依赖列表
* @param {function} factory 模块方法
*/
function define(id,deps,factory){
if(modules[id]){
throw "module " + id + " 模块已存在!";
} //如果有依赖
if(arguments.length > 2){
modules[id] = {
id : id,
deps : deps,
factory : factory
}
}else{
modules[id] = {
id : id,
factory : deps
}
}
} window.require = require;
window.define = define; })();
这里的实现忽略了javascript文件的加载和循环依赖的问题,因为我的重点是放在模块之间的导入和团队协作开发的问题上。个人认为也没有必要做成按需加载,因为不会用到的模块,是根本没有必要为它编写模块的。在开发模式下,需要一个功能,就在index.html中导入一个js模块,在产品模式下,就把index.html中的所有js文件进行合并压缩,这样做到只加载一次,根本没有按需加载出场的余地。
下面我来介绍下这个简易版的require.js框架是怎么用的。
其实非常简单,只要知道define()是定义一个模块,require是调用一个模块就可以了。
具体的看下面几个典型示例:
//定义一个模块 a
define('a',function(){
//模块a中的私有变量
var name = 'module a'; //返回这个模块所实现的方法
return {
a : function(){
//根据需要返回模块中的内容
console.log(name)
}
}
})
//定义一个模块 b
define('b',function(){
var name = 'module b'; return {
b : function(){
console.log(name)
}
}
}) //定义一个模块 c, 依赖 a,b两个模块
define('c',['a','b'],function(a,b){
//a,b是模块c需要依赖的模块
//只要以数组的方式依次例出模块Id就可以了
console.log(a,b)
return {
c : function(){
console.log('module c')
}
}
}) //同时调用a,b两个模块
require(['a','b'],function(a,b){
//a就是前面定义的模块a返回的对象
//b同上
//此处就可以自由的使用a,b模块中所公布的方法了
console.log(a,b)
}) //调用c模块
require('c',function(c){
console.log(c)
})
这样我们就实现了一个简单的模块化功能。从上面的示例来看,进行单元测试是不是很容易呢? 代码看起来是不是很清爽呢?
从我这一百来行的代码中也不难看出实现的原理,先是定义modules用来收集所定义的模块,然后用 define来定义一个模块,通过id进行区分,factory返回这个模块提供的对象
而require则根据请求的id,去modules中进行查找,如果没有依赖则直接返回找到的对象,如果有依赖,则先查找依赖模块中返回的对象,最后把它们传给回调函数。其中还考虑了id是数组的情况,也就是可以同时请求多个模块,这也只不过是循环解析这个过程而已。有些书上说,define就像是内鬼,而require则是侵略者。它们之间的关系就是里应外合,说的非常形象。浓缩才是精华,从这一百来行的代码中体验模块化的实现原理,是不是要比从几千行的require.js 中慢慢模索要容易地多呢?
如果您觉得这文章对您有帮助,请点击推荐!谢谢,想跟我一起进步么?那就【关注】我吧!
高级javascript---模块化编程的更多相关文章
- Javascript模块化编程(三):require.js的用法
Javascript模块化编程(三):require.js的用法 原文地址:http://www.ruanyifeng.com/blog/2012/11/require_js.html 作者: 阮一峰 ...
- Javascript模块化编程(二):AMD规范
Javascript模块化编程(二):AMD规范 作者: 阮一峰 原文地址:http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_d ...
- Javascript模块化编程(一):模块的写法
Javascript模块化编程(一):模块的写法 作者: 阮一峰 原文链接:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html ...
- Javascript模块化编程(二):AMD规范(转)
这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要 ...
- Javascript模块化编程(一):模块的写法(转)
随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个团队分工协作.进度管理.单元测试等等......开发者 ...
- Javascript模块化编程(二):AMD规范 作者: 阮一峰
声明:转载自阮一峰的网络日志 这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可 ...
- Javascript模块化编程(一):模块的写法 作者: 阮一峰
声明:转载自阮一峰的网络日志 随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个团队分工协作.进度管理. ...
- Javascript模块化编程之路——(require.js)
转自:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html Javascript模块化编程(一):模块的写法 随着网站逐渐变成&q ...
- Javascript模块化编程(一):模块的写法 (转载 学习中。。。。)
转载地址:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html 阮一峰 大神:http://www.ruanyifeng.com/ ...
- Javascript模块化编程(二):AMD规范【转】
作者: 阮一峰 日期: 2012年10月30日 这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为 ...
随机推荐
- jquery常见获取高度
jquery获取文档高度和窗口高度,$(document).height().$(window).height() $(document).height():整个网页的文档高度 $(window).h ...
- 对象的this引用
Java中的this关键字总是指向调用该方法的对象.根据this出现位置的不同,this作为对象的默认引用有两个功能: 1.构造器中引用该构造器正在初始化的对象. 2.在方法中引用调用该方法的对象. ...
- System.Web.HttpContext.Current.Session为NULL解决方法
http://www.cnblogs.com/tianguook/archive/2010/09/27/1836988.html 自定义 HTTP 处理程序,从IHttpHandler继承,在写Sys ...
- AOP面向切面编程的四种实现
一.AOP(面向切面编程)的四种实现分别为最原始的经典AOP.代理工厂bean(ProxyFacteryBean)和默认自动代理DefaultAdvisorAutoProxyCreator以及Bea ...
- 最短路(代码来源于kuangbin和百度)
最短路 最短路有多种算法,常见的有一下几种:Dijstra.Floyd.Bellman-Ford,其中Dijstra和Bellman-Ford还有优化:Dijstra可以用优先队列(或者堆)优化,Be ...
- 五、jquery使用工具函数
工具函数对应的网址在 http://api.jquery.com/categouy/utilities/ 工具函数处理对象的不同,可以将其分为几大类别:浏览器的检测.数组和对象的操作.字符串的操作 ...
- Sage Crm 权限原理分析
文字是11年写的,贴出来共享一下,先来一张表结构图: 一.区域.表名:[territories] 1.我们先来看看区域表的结构. 从图中前面都是不能为空的字段,都是很重要的.来介绍一下这些字段: Te ...
- ubuntu自定义分辨率
首先说下为啥要专门敲个文章来说明这个问题,因为我最近入手了一台分辨率为3200*1800的高分辨率笔记本,但使用的时候发现现在的操作系统及其诸多软件对高分辨率屏幕的支持真的是太烂,字体发虚或者变得非常 ...
- 学习笔记:java线程安全
首先得明白什么是线程安全: 线程安全是编程中的术语,指某个函数 (计算机科学).函数库在多线程环境中被调用时,能够正确地处理各个线程的局部变量,使程序功能正确完成. 这是维基百科里的资料,看完后还不是 ...
- CYQ.Data 快速开发之UI(赋值、取值、绑定)原理
昨夜园子猴子问了几个我CYQ.Data使用的小问题,经过简单解答后,他表示“妈妈再也不用担心我的学习",并于事后以资鼓励,希望这框架越走越好. 除了技术上的交流,双方在生活,S上面的问题上也 ...