JS模块化开发:使用SeaJs高效构建页面

1、扯淡部分javascript

好久好久之前,也就是刚开始接触前端的那会儿,脑壳里压根没有什么架构、重构、性能这些概念,天真地觉得前端===好看的页面,甚至把js都划分到除了用来写一些美美的特效别无它用的阴暗角落里,就更别说会知道js还有面向对象,设计模式,MVC,MVVM,模块化,构建工具等等这些高大上的概念了。如今想一想还真是Too young too naive。前两天某大神在群里分享他招聘前端的心得的时候就说,就是那些觉得能写两个页面就能够自称前端的人拉低了行业水平。这样看来前两年我还真的扯了很多后腿呢……html

后来干这行干得稍久一些,发现水简直深深深深千尺,并且周围遍及沼泽。即使爬到岸上,迎接你的又是大大小小各类坑。坑爹的IE6,坑爹的兼容,坑爹的浏览器特性……总之,任何一个前端都有被这些大大小小的坑虐到体无完肤的惨痛经历。但(我以为这个但字是点睛之笔),生活在继续,时代在发展,竞争依然残酷,你不往前走就只能在这片沼泽里不断下沉,最后挣扎的结果也不过是冒出水面两个泡泡而后……爆掉。前端

在经历了会写页面,会用js写效果的阶段后,大多数人都已经慢慢地可以知足产品提出的各类奇葩的功能需求,但仅仅是知足了需求,而没有考虑性能、团队协做、开发消耗的各类成本等等这些问题。有时候甚至写好的js再回头去看时也会让本身一头雾水:各类方法,各类逻辑杂乱无章地纠缠在一块儿,根本理不清谁调用了谁,谁为谁定义,谁又是谁的谁!更可怕的是当项目被其余小伙伴接管,每修改一处上线前都担惊受怕:修改这里到底TM对不对啊?java

还好前端领域开路者们用他们的智慧朝咱们艰难跋涉的水坑里扔了几块石头:尝试让你的代码模块化吧~node

 

2、js模块化git

为毛要尝试模块化开发?github

现在的网页愈来愈像桌面程序,网页上加载的javascript也愈来愈复杂,coder们不得不开始用软件工程的思惟去管理本身的代码。Javascript模块化编程,已经成为一个很是迫切的需求。理想状况下,开发者只须要实现核心的业务逻辑,其余均可以加载别人已经写好的模块。可是,Javascript不是一种模块化编程语言,它不支持"类"(class),更遑论"模块"(module)了。(正在制定中的ECMAScript标准第六版将正式支持"类"和"模块",但还须要很长时间才能投入实用。)web

——来自阮一峰的博文:《Javascript模块化编程(一):模块的写法编程

上面其实已经把模块化的意义和目的已经讲述的很清楚了,因此就拿来主义,节省脑细胞留给下面的内容设计模式

模块化的概念出来之后,新的问题又来了:需不须要一个统一的模块化标准?咱们来试想一下若是没有标准的状况:A以本身的标准写了模块Module1,而后B又以本身的标准写了Module2,恩,在他们看来,这的确是模块,但当Module1想调用模块Module2的时候该怎么调用呢?它们之间火星人与地球人交流,没有同声传译看起来依旧是毫无头绪。因而模块化规范便又成了一个问题。

2009年美国的一位大神发明了node.js (具体内容自行脑补,本文不做讨论),用来开发服务器端的js。咱们都知道,传统的服务器端开发语言如PHP、JAVA等都必须进行模块化开发,JS想占据人家的地盘也不例外,模块化是必须的,因而commomJS模块化开发规范诞生了,但这货只是服务器端JS模块化开发的标准,客户端又没用。

—有童鞋:bla了那么多,这跟我在客户端进行js模块化开发有毛关系啊?

—PO主:表着急,了解了这玩意儿的前世此生,用起来才能驾轻就熟~

服务器端JS模块化规范有了,JSer们天然想到了能把commonJS规范拿到客户端就好啦,并且最好二者可以兼容,一个模块不用修改,在服务器和浏览器均可以运行。爽爆~但(这个但字又是一个点睛之笔),因为一个重大的局限,使得CommonJS规范不适用于浏览器环境。服务器端获取资源的方式是本地读取,而客户端拿资源的方式是经过Http来获取,这是一个大问题,由于模块都放在服务器端,浏览器等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。所以,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous),因而诞生了AMD和CMD。

—有童鞋:核心内容终于TMD来了,就是AMD和CMD这二货

—PO主:……

 

3、AMD和CMD

AMD (Asynchronous Module Definition) :  RequireJS 在推广过程当中对模块定义的规范化产出。

AMD用白话文讲就是 异步模块定义,对于 JSer 来讲,异步是再也熟悉不过的词了,全部的模块将被异步加载,模块加载不影响后面语句运行。全部依赖某些模块的语句均放置在回调函数中,等到依赖的模块加载完成以后,这个回调函数才会运行。

主要有两个Javascript库实现了AMD规范:require.jscurl.js

(本文主要分享的是SeaJs模块化构建方式,关于requireJs构建方式请移步至:《Javascript模块化编程(一):模块的写法》)

 

CMD (Common Module Definition) : SeaJS 在推广过程当中对模块定义的规范化产出。

实现了CMD规范的主要的Javascript库:Sea.js

CMD翻译来就是 通用模块定义,与AMD的相同点:

1. 这些规范的目的都是为了 JavaScript 的模块化开发,特别是在浏览器端的。

2. 目前这些规范的实现都能达成浏览器端模块化开发的目的

固然与AMD也有有两点区别:

1. 对于依赖的模块,AMD 是提早执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改为能够延迟执行(根据写法不一样,处理方式不一样)。CMD 推崇 as lazy as possible(PO主:是越懒越好的意思么?)。

2. CMD 推崇依赖就近,AMD 推崇依赖前置

——SeaJs做者玉伯在知乎的回答

看代码理解上面两点的意思:

AMD模块的定义方法

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好,即依赖前置,执行完引入的模块后才开始执行回调函数
    a.doSomething()
    // 此处略去 100 行
    b.doSomething()
    ...
})

CMD模块的定义方法:

// CMD
define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething()
    // 此处略去 100 行
    var b = require('./b') // 依赖能够就近书写,即依赖就近,何时用到何时才引入
    b.doSomething()
    // ... 
})

好了,看过两个例子,对于以前没有接触过模块化开发的童鞋来讲依旧是一头雾水:那个define是什么东东啊?还有那个require,exports,module,都是干什么的?表捉急,咱们一步一步来。

在 CMD 规范中,一个模块就是一个文件。代码的书写格式以下:

define(factory);

来看github上CMD模块定义规范上的解释:

define 是一个全局函数,用来定义模块。

define 接受 factory 参数,factory 能够是一个函数,也能够是一个对象或字符串

factory 为对象、字符串时,表示模块的接口就是该对象、字符串。好比能够以下定义一个 JSON 数据模块:

1 define({ "foo": "bar" });

也能够经过字符串定义模板模块:

1 define('I am a template. My name is {{name}}.');

factory 为函数时,表示是模块的构造方法。执行该构造方法,能够获得模块向外提供的接口。factory 方法在执行时,默认会传入三个参数:require、exports 和 module

1 define(function(require, exports, module) {
2   // 模块代码
3 });
 
4、小例子
 
说了半天概念应该印象还不深入,咱们就来看一个例子用来演示sea.js的基本用法。首先define传入的参数是对象和字符串的状况,我先举一个参数的对象的例子,传字符串大同小异。来看代码:
 
1,我先来定义一个模块m1.js:
define({a:"这里是属性a的值"});

define传入的是一个对象字面量。如今这个东东就能够叫作一个模块了~我想在页面一加载的时候就把a的值alert出来,怎么作呢?继续往下看。

2,在页面上引入这个模块:

1 seajs.use('./m1.js',function(ex){
2      alert(ex.a);
3  }); //弹出“这里是属性a的值”

翻译得直白一点,大意就是:

seajs : Hi~m1.js,我如今要用(use)你了,而后把你的公开接口(exports)存到我回调函数的参数(ex)里,你把想给我调用的东东放到这个参数里吧~么么哒

m1.js : 好的,我定义的对象字面量放到接口里给你了,拿去尽管刷~

而后……a的值就弹出来了。很愉快的一次交易。PS:页面所调用的模块就为整个web应用的js入口。本例中js的入口就是m1.js。接下来再来看看若是define的参数是个函数的状况。
 
1,先定义一个模块m2.js:
1 define(function(require,exports,module){
2     var var1 = "这是要alert出来的值";//私有变量,没有经过接口返出去的其余模块不能访问
3     function alerts(){
4         alert(var1);
5     }
6     exports.alerts = alerts;//将须要公开的方法存入exports接口中
7 });

2,在页面上引入这个模块并执行模块m2.js公开的方法:

1 seajs.use('./m2.js',function(ex){
2      ex.alerts();//ex中存的有m2.js中的公开对象
3 }); //弹出“这是要alert出来的值”

到这里能够简单地说一下factory方法的三个形参的意义了(我的理解):

require : 提供了引入机制,提供了一种方式来创建依赖,和C中的include和java中的import相似;

exports : 提供了导出机制,提供了私有和共有分离,未使用exports语句导出的变量或者函数,其余模块即便引用此模块也不能使用;

module : 提供了模块信息描述。

是否是思路贱贱清晰了呢?刚才咱们的例子中只是从页面调用模块的用法,模块之间互相调用尚未体现,SO,接下来就以m1.js和m2.js两个模块做为例子来尝试一下 模块之间互相调用

1,首先m1.js模块不变:

1 define({a:"这里是属性a的值"});

2,m2.js模块要依赖(require)m1.js:

1 define(function(require,exports,module){
2     var var1 = "这是要alert出来的值";//私有变量,没有经过接口返出去的其余模块不能访问
3     var var2 = require('./m1.js').a;//这里就是m2.js模块调用m1.js的方式:var2的值等于当前模块所依赖的m1.js对外接口中属性a的值
4     function alerts(){
5         alert(var2);
6     }
7     exports.alerts = alerts;//将须要公开的方法存入exports接口中
8 });

3,页面上引入m2.js模块(同上一个例子),结果就会把a的属性值给alert出来~

 

5、实例:模块化的拖拽个窗口缩放

 

固然,上面几个例子是简单到不能再简单的例子,估计亲们也已经看出来一些道道,但我的感受仍是没能体现出模块化开发的优点。那下面就来看一个实例:模块化的拖拽个窗口缩放。先看一下效果图:

PS:效果图中的红色区域要先定缩放的范围,即宽高0px-宽高500px。要写这样一个需求的例子,按照以前的编程习惯你会怎么写?反正在以前,我是会把全部的功能写到一个js文件里,效果出来就行,随大家怎么胡搅蛮缠。而自从认识了模块化开发,心里不止一次告诉本身,拿到需求bigger必定要高,必定要高(虽然require.js和sea.js这两个东东在圈内多多少少仍是有些争议)……

废话少说,首先来分析一下须要划分多少个模块吧:

1,一开始就要有个入口模块的吧?恩,必须的!入口模块Get√~

2,既然是拖拽,要有个拖拽模块吧?恩,必须的!拖拽模块Get√~

3,既然要缩放,要有个缩放模块吧?恩,必须的!缩放模块Get√~

4,既然限定缩放范围<=500px,那还要有个限定缩放范围的模块吧?恩,这个能够有,但为了之后调整范围数值方便,仍是单列个模块吧。限定缩放范围模块Get√~

到这里咱们就把本需求划分红了四个模块:

·  入口模块:main.js

·  拖拽模块:drag.js

·  缩放模块:scale.js

·  限定缩放范围模块:range.js

首先,是页面引入入口模块(我尽可能把注释都写在代码中,以便对照代码,这样也就不用写大片大片的文字了~):
1  <script>
2     seajs.use('./js/main.js');//没有callback函数代表引入后直接执行入口模块
3 </script>

接下来看看入口模块(main.js)里都应该有些神马东东吧:

 1 //入口模块
 2 define(function(require,exports,module){
 3     var $id = function(_id){return document.getElementById(_id);}
 4     var oInput = $id("button1");
 5     var div1 = $id("div1");
 6     var div2 = $id("div2");
 7     var div3 = $id("div3");//以上是获取页面元素的几只变量
 8     require('./drag.js').drag(div3);//引入拖拽模块,执行拖拽模块接口中的drag方法并传参
 9     exports.oInput = oInput;
10     oInput.onclick = function(){
11         div1.style.display = "block";
12         require('./scale.js').scale(div1,div2);//引入缩放模块,执行缩放模块接口中的scale方法并传参
13     }
14 });

恩,还真是全面呢,把拖拽模块和缩放模块都引进来了。看看拖拽模块(drag.js)吧~

 1 //拖拽模块
 2 define(function(require,exports,module){
 3     //这个方法就是实现拖拽的方法,不用详述了吧?
 4     function drag(obj){
 5         var disX = 0;
 6         var disY = 0;
 7         obj.onmousedown = function(e){
 8             var e = e || window.event;
 9             disX = e.clientX - obj.offsetLeft;
10             disY = e.clientY - obj.offsetTop;
11             document.onmousemove = function(e){
12                 var e = e || window.event;
13                 var l = require('./range.js').range(e.clientX - disX, document.documentElement.clientWidth - obj.offsetWidth,0);
14                 var t = require('./range.js').range(e.clientY - disY, document.documentElement.clientHeight - obj.offsetHeight,0);
15                 obj.style.left = l + "px";
16                 obj.style.top = t + "px";
17             }
18             document.onmouseup = function(){
19                 document.onmousemove = null;
20                 document.onmouseup = null;
21             }
22         }
23     }
24     exports.drag = drag;//返回拖拽模块中想要被公开的对象,也就是在本模块中定义的drag方法。注意有参数~
25 });

接下来是缩放模块(scale.js)。缩放模块还须要调用 限定缩放范围模块 (range.js) 的哦~这点不要搞忘了。

 1 //缩放模块
 2 define(function(require,exports,module){
 3     //这个方法就是obj2控制obj1改变大小的方法,也再也不详述啦~
 4     function scale(obj1,obj2){
 5         var disX = 0;
 6         var disY = 0;
 7         var disW = 0;
 8         var disH = 0;
 9         obj2.onmousedown = function(e){
10             var e = e || window.event;
11             disX = e.clientX;
12             disY = e.clientY;
13             disW = obj1.offsetWidth;
14             disH = obj1.offsetHeight;
15             document.onmousemove = function(e){
16                 var e = e || window.event;
17                 var w = require('./range.js').range(e.clientX - disX + disW,500,100);//看这里看这里,引入了限定范围的range.js模块~
18                 var h = require('./range.js').range(e.clientY - disY + disH,500,100);
19                 obj1.style.width = w + "px";
20                 obj1.style.height = h + "px";
21             }
22             document.onmouseup = function(){
23                 document.onmousemove = null;
24                 document.onmouseup = null;
25             }
26         }
27     }
28     exports.scale = scale;//将须要公开的对象存入模块接口中,以便其余模块调用~
29 });

最后就是限定范围的模块(range.js)了。

 1 //限定拖拽的范围模块
 2 define(function(require,exports,module){
 3     function range(inum,imax,imin){
 4         if(inum > imax){
 5             return imax;
 6         }else if(inum < imin){
 7             return imin;
 8         }else{
 9             return inum;
10         }
11     }
12     exports.range = range;
13 });

这就是模块化,虽然在这个实例中咱们用到了4个js,但在页面上咱们只引入了一个入口模块main.js,其余模块都会按需自动引入(以下图所示),并且每一个功能模块的区分特别清晰,不再用担忧神马命名冲突啊、依赖混乱啊之类的,并且团队小伙伴每人负责一个模块,只要放出当前模块的公开接口并提供简要的说明文档(由于标准统一),其余小伙伴们写的模块就能很是方便地调用到你写的模块,连修改的时候都不用考虑对其余功能的影响,变得更大胆了呢~

查看完整DEMO请猛戳

 
写在最后
 
其实本文介绍的模块化和seajs的使用依旧比较浅显,但基本的模块化思想已经融入到例子中了。 若是你经历过前文所述的之前写js逻辑的各类纠结各类坑爹,不妨尝试一下将你的代码模块化,那将是一种飞同样的感受……本文最后会为你们列出一些相关的资料,想深刻了解的小伙伴们能够果断收走~
 
SeaJs官网 : http://seajs.org/docs/

CMD 模块定义规范:https://github.com/seajs/seajs/issues/242

玉伯:AMD和CMD的区别:http://www.zhihu.com/question/20351507/answer/14859415 

AMD and CMD are dead之js模块化黑魔法 : http://www.cnblogs.com/iamzhanglei/p/3790346.html

(后续会继续补充……)

相关文章
相关标签/搜索