前言
現(xiàn)在學(xué)習(xí)到webpack锦亦,然后重新回過頭來回顧前端模塊化一路發(fā)展過程定義的規(guī)范,遂寫下這篇文令境。
一杠园、模塊化的價值
適用:功能繁多的單頁面(富應(yīng)用頁面)
多人合作的時候,如果都寫成js文件script標(biāo)簽引入舔庶,那么就要關(guān)心js的順序抛蚁,避免被覆蓋,以及關(guān)心變量惕橙,會不會沖突瞧甩。都是全局變量,很容易沖突吕漂。這在代碼量大亲配,多人協(xié)作的情況下,很災(zāi)難。
同時吼虎,多人協(xié)作的大項(xiàng)目上犬钢,js文件很多,彼此之間可能會有依賴關(guān)系思灰,比如某個js需要先加載jquery之后才生效玷犹,這樣的。js文件一多洒疚,順序擺放就很重要歹颓,不然依賴關(guān)系就會亂掉,代碼可能就不會發(fā)生作用油湖。這樣可能需要做js文件的整理記錄以及位置順序記錄巍扛,這樣很麻煩,對新人也不友好乏德。浪費(fèi)不必要精力撤奸。
富應(yīng)用的頁面越來越多,很多頁面邏輯遷移到客戶端實(shí)現(xiàn)喊括,前端代碼越來越多胧瓜。單薄的javascript,無法支撐代碼組織的燃眉之急郑什。
那么前端開發(fā)者們就開始想著使用JavaScript來模擬其他后臺開發(fā)語言(例如:Java)的代碼組織方式府喳,例如package(包):將邏輯相關(guān)的代碼放到同一個包中,每個包之間互不影響蘑拯,即使包內(nèi)變量命名相同也不沖突钝满。使用包時,就import加載使用申窘。
在JavaScript中要實(shí)現(xiàn)類似的功能舱沧,最先想到的是函數(shù)。將所有函數(shù)抽離出來放置到一個js文件中偶洋,然后引用js文件位置,調(diào)用函數(shù)距糖。但這個方式玄窝,還是無法解決命名沖突的問題,函數(shù)的命名還是要避免沖突悍引。這種純函數(shù)的方式恩脂,需要排除。
那么就來看對象的方式趣斤。
對象封裝寫法一:
<script>
/*封裝為一個對象*/
var block_mock={
a:1,
b:2,
fn1:function(){
//dosomething
}
fn2:function(){
//dosomething
}
}
/*調(diào)用的寫法*/
block_mock.fn1();
block_mock.fn2();
/*存在問題:對象內(nèi)的變量可以被隨意更改*/
block_mock.a=100;
</script>
對于寫法一存在的問題俩块,我們來看寫法二:
/*改進(jìn)寫法:寫成一個立即執(zhí)行函數(shù),開始具備模塊化的感覺*/
var block_mock=(function(){
var a=1;/*局部變量,函數(shù)fn1,fn2可以使用玉凯,避免全局污染*/
var b=2;
function fn1(){
//dosomething
};
function fn2(){
//dosomething
};
return{
fn1:fn1,
fn2:fn2
};
})()
/*調(diào)用的寫法*/
block_mock.fn1;
block_mock.fn2;
寫法二中势腮,return是最重要一步,return出來,等于只暴露這一塊漫仆,在模塊外部無法修改我們沒有暴露的變量和函數(shù)捎拯。
使用對象封裝的寫法,我們可以看到JavaScript具備模塊化的基礎(chǔ)盲厌,來達(dá)到隔離和組裝復(fù)雜代碼的作用署照。繼而,后續(xù)就發(fā)展出完善的前端模塊化的規(guī)范吗浩。這些發(fā)展接著往下講建芙。
往下講之前,我們先結(jié)合前面講的這一堆懂扼,總結(jié)下模塊化的實(shí)現(xiàn)禁荸,帶給我們的預(yù)期好處:
- 解決命名沖突問題
- 解決繁瑣的文件依賴問題,實(shí)現(xiàn)管理
- 代碼能分塊出來微王,可讀性提高了屡限。想修改代碼,也不用從頭到尾找炕倘,只需要找到對應(yīng)的模塊代碼修改就行
- 提高代碼復(fù)用性:比如曝光組件抽象出來實(shí)現(xiàn)ajax+懶加載+無限加載功能钧大。
二、JavaScript的模塊規(guī)范的發(fā)展
1罩旋、CommonJS
服務(wù)器端的JavaScript——NodeJS在服務(wù)端實(shí)現(xiàn)第一個模塊化的規(guī)范:CommonJS啊央。
具體用法是:
1、模塊定義:根據(jù)CommonJS規(guī)范涨醋,一個單獨(dú)的js文件就是一個模塊瓜饥。一個模塊是一個單獨(dú)的作用域,模塊內(nèi)部定義的變量浴骂,外部模塊無法訪問乓土。
2、模塊輸出:既然外部模塊無法訪問溯警,就需要我模塊本身主動輸出趣苏,這個模塊才是有意義的。按照規(guī)范使用module.exports
對象梯轻。
3食磕、模塊訪問:想要加載某個模塊時,使用require
方法喳挑,該方法讀取一個文件并執(zhí)行彬伦,返回文件內(nèi)部的module.exports
對象滔悉。
看下面例子:
注意:下面例子是js文件,在node端運(yùn)行node+文件名
单绑,執(zhí)行出結(jié)果回官。
定義模塊a,并輸出:
var people={
name:'hyh',
sayName:function(){
console.log(this.name);
}
}
/*最重要,輸出模塊*/
module.exports=people;
定義模塊c,并輸出:
var rabot={
name:'I am a ranbot',
walking:function(){
console.log('I can walking');
}
}
module.exports=rabot;
注意:測試在a.js里寫多一個rabot對象時询张,前面寫的people對象孙乖,在執(zhí)行module.exports=people時會被后面寫的對象module.exports=rabot覆蓋而無效,所以只能是一個js文件定義一個對象份氧,作為一個模塊唯袄。
定義模塊b,并在模塊b中加載模塊a和b,使用模塊a和b的方法:
var p=require('./a');
var r=require('./c');
console.log(p);
p.sayName();
r.walking();
console.log('hahahahahaha');
在git后臺中執(zhí)行js文件b.js蜗帜,得到執(zhí)行結(jié)果:
注意:見代碼可知調(diào)用的js——b.js恋拷,是可以同時調(diào)用多個js來使用。
CommonJS是在NodeJS服務(wù)端運(yùn)行使用的規(guī)范厅缺。使用CommonJS規(guī)范的js文件調(diào)用蔬顾,是一個同步的過程,b.js執(zhí)行模塊內(nèi)容湘捎,是需要在本地讀取同個文件夾下的a.js文件和c.js文件诀豁,然后進(jìn)行操作。
CommonJS的這套過程暴露了缺點(diǎn)窥妇,不適合在瀏覽器端執(zhí)行:
1舷胜、實(shí)現(xiàn)模式是同步的,需要將所有require都下載下來活翩,然后才執(zhí)行p.sayName()這樣具體的函數(shù)語句烹骨。如果在瀏覽器端做同步實(shí)現(xiàn),那用戶需要等到全部的js文件都下載完成之后材泄,才可以執(zhí)行操作沮焕。這樣的長時間等待,顯然讓用戶抓狂拉宗。而且瀏覽器端的script標(biāo)簽天生是異步的峦树,所有的js文件下載時無法保證按照定義的依賴順序來下載,那么就未免會產(chǎn)生錯誤旦事,導(dǎo)致頁面無效空入。
2、在瀏覽器端族檬,路徑很難定義,不再是像服務(wù)器一樣化戳,從本地讀取require('./a')单料,這樣的路徑寫法埋凯。
所以,雖然CommonJS是第一個出現(xiàn)的JavaScript模塊化規(guī)范扫尖,但卻是不適用于瀏覽端使用的白对。所以還是看往后發(fā)展的AMD規(guī)范:
2、AMD規(guī)范
AMD 即Asynchronous Module Definition换怖,中文名是異步模塊定義甩恼。ADM規(guī)范其實(shí)是使用requireJS框架的模塊化寫法時要求的規(guī)范,是在瀏覽器端實(shí)現(xiàn)模塊化開發(fā)的規(guī)范沉颂。
具體用法有:
1条摸、定義模塊:
define(id?[dependencies]?factory)
,使用定義的define函數(shù)來定義模塊铸屉。
id和dependencies為可填項(xiàng)钉蒲,如果不設(shè)定id,加載這個模塊時彻坛,就默認(rèn)使用這個模塊的文件命名顷啼,否則使用id。dependencies為當(dāng)前這個模塊會使用到的依賴昌屉。factory是必填項(xiàng)钙蒙,為模塊的主體內(nèi)容〖渫裕可以是函數(shù)躬厌,也可以是對象。如果是函數(shù)蜻牢,只會被執(zhí)行一次烤咧。如果是對象,則是這個模塊的輸出值抢呆。
2煮嫌、加載模塊:
require([dependencies],function(){})
require()函數(shù)接收兩個參數(shù):第一個參數(shù)是一個數(shù)組,表示所依賴的模塊抱虐。第二個參數(shù)是一個回調(diào)函數(shù)昌阿,當(dāng)前面指定的模塊都加載成功后,它將被調(diào)用恳邀。加載的模塊會以參數(shù)形式傳入該函數(shù)懦冰,從而在回調(diào)函數(shù)內(nèi)部就可以使用這些模塊。
** 重點(diǎn):require()函數(shù)在加載依賴的函數(shù)的時候是異步加載的谣沸,這樣瀏覽器不會失去響應(yīng)刷钢,它指定的回調(diào)函數(shù),只有前面的模塊都加載成功后乳附,才會運(yùn)行内地,解決了依賴性的問題伴澄。**
也就是requireJS實(shí)現(xiàn)滿足瀏覽器的異步要求,避免了因?yàn)榧虞djs文件而頁面停止渲染阱缓,加載文件越多非凌,頁面失去響應(yīng)時間越長的問題了。同時使用require.JS荆针,我們開始不用操心因?yàn)閖s文件間的依賴關(guān)系而需要關(guān)注的文件調(diào)用順序了敞嗡。因?yàn)樗行枰囊蕾囋跀?shù)組中指定后加載,只有加載成功了航背,回調(diào)函數(shù)才會執(zhí)行喉悴。
使用例子:
define('sayname',[],function(){
var name = 'Byron';
function sayName(){
console.log(name);
}
return {
sayName: sayName
};
}());
require(['jquery','sayname'], function($,my){
my.sayName();
});
$和my參數(shù)就是jquery執(zhí)行后,以及sayname執(zhí)行后返回的return的內(nèi)容沃粗。需要在requireJS框架下執(zhí)行粥惧,因?yàn)闉g覽器不支持require函數(shù)。
但AMD這樣的規(guī)范寫法最盅,需要我一次把所有依賴寫在數(shù)組里突雪,當(dāng)依賴特別多時,就麻煩凸顯涡贱。所以我們就再追求實(shí)現(xiàn)咏删,在仍然支持依賴異步加載的基礎(chǔ)上,對依賴能夠按需加載。順著這個思路,就講到CMD規(guī)范:
3沽瞭、CMD規(guī)范
相對于AMD,最重要的就是實(shí)現(xiàn)按需加載js文件依賴
CMD是基于Sea.js的框架的實(shí)現(xiàn)的要求寫法辰狡,現(xiàn)在Sea.js已經(jīng)廢棄不用了,僅作了解垄分。
看例子:
// 定義模塊 myModule.js
define(function(require, exports, module) {
var $ = require('jquery.js')
$('div').addClass('active');
var timeout=require('...') /*依賴文件所在路徑*/
timeout.init()
});
seajs.use(['myModule.js'], function(my){
//do something
});
看栗子可知宛篇,CMD是需要用到什么,才require什么薄湿,屬于懶執(zhí)行叫倍。AMD對待依賴的態(tài)度是預(yù)執(zhí)行。
結(jié)語
作為前端模塊化豺瘤,組件化的基礎(chǔ)吆倦,所以需要回看一路發(fā)展的規(guī)范。
發(fā)展到現(xiàn)在坐求,AMD和CMD已經(jīng)實(shí)現(xiàn)相互支持了蚕泽。在requireJS中也可以使用CMD規(guī)范的寫法,實(shí)現(xiàn)相同的效果:例如回到頂部功能的實(shí)現(xiàn)
現(xiàn)在requireJS也是不怎么用了桥嗤,發(fā)展為主流的webpack也是支持CMD和AMD規(guī)范的赛糟。
var React = require('react');
var MyComponent = React.createClass({
// do something
});
module.exports = MyComponent;