JS模塊:初學者指南
如果你是一個學習js的新手,一些專業(yè)的術(shù)語:模塊化打包與模塊化載入,webpack與browserify,AMD和CommonJS.這些問題會讓人很迷惑.
js模塊化系統(tǒng)可能比較難以理解,但是這些對于網(wǎng)絡(luò)開發(fā)者確實必須的.
在這片文章中,我會盡量用平實的語言來解釋這些抽象的詞匯,希望對你有所幫助!
第一部分:能有人給我解釋一下什么是模塊化?
好的作者會把他們的書分為章節(jié)和段落,而好的碼農(nóng)會把他們的程序模塊化.
就像一片文章的章節(jié),模塊就是一組單詞.
對于一個好的模塊,是具有完備的,不同的功能的,他們可以被拖拽,移除,添加而不會使系統(tǒng)分裂.
為什么使用模塊
針對不規(guī)則的,相互依賴的代碼使用模塊化會有很多的好處,我認為最重要的點:
-
可維護性:定義了一個完備的模塊,一個好的模塊旨在于減少各個部分的依賴關(guān)系,他可以獨立的成長和發(fā)展.更新一個單獨的模塊兒會更容易,當其已經(jīng)從其他代碼解耦后,
回到書的例子,如果你想要更新書的一個章節(jié),如果一個小的章節(jié)變化需要你去更改相關(guān)的每個章節(jié),name這將是一個噩夢,但是,如果你以模塊化的方式去寫每個章節(jié)會改善而不影響其他的章節(jié)
-
命名空間:在JavaScript中,外層函數(shù)作用域外的變量是全局的(意味著每個人可以訪問),由于這一點就很容易造成命名污染:完全不相關(guān)的代碼共享使用了全局變量.
在不相關(guān)的代碼之間共享全局變量在開發(fā)當中是萬萬不適用的.
在之后我們將看到模塊化允許我們通過創(chuàng)建變量的私人空間避免命名污染.
-
可重用性:這里是事實:我們都會復制我們之前所寫的代碼到新的項目當中,舉個例子,我們假設(shè)你復制了你之前所寫的所有的工具方法到你當前的項目.
這都是很好的,但是如果你找到了一個更好的方法去更新這些代碼,你得去回顧并且熟記跟新他,
顯然這個是非常浪費時間的,難道沒有更容易的方法--等等--一個模塊我們可以不斷的重復使用嗎?
如何合并模塊
有很多方法去合并模塊到你的代碼當中,來大概瀏覽一下:
模塊化模型
模塊化模型常被用作模擬類的概念,我們可以存儲公共和私用的方法和變量在一個單獨的項目當中,就像在Java和python當中使用class一樣,允許我們創(chuàng)建一個們想要展示的公共的API方法,仍然可以封裝私人變量和方法在一個閉包的作用域.
有幾種方法去實現(xiàn)模塊化模型,在第一個例子當中,我將要用一個匿名的閉包,這將幫助我們達成我們的目標通過放置我們的到嗎在匿名函數(shù)當中(記住:在JavaScript當中,函數(shù)是唯一的方法創(chuàng)建新的作用域)
例子1:匿名閉包
(
function(){
//保存這些變量在這個閉包的作用域當中
var myGrades = [ 93,95,88,0,55,91];
var average = function(){
var totle = myGrades.reduce(function(acu,item){
return acu + item;
},0);
return 'Your average gride is '+totle/myGrades.length+',';
}
var failing = function(){
var failingGrade = myGrades.filter(function(item){
return item<70;
});
return 'You failed'+failingGrade.length+'times.';
}
console.log(failing());
}());
//You failed 2 times.
通過這個構(gòu)造器,我們的匿名函數(shù)有了他自己的運行環(huán)境or閉包,然后立即執(zhí)行.我們隱藏了變量從全局的命名空間.
這種方法最好的一點是你可以使用本地的變量inside這個函數(shù)不會偶然的復寫全局變量,但是仍然可以訪問全局變量,就像:
var global = 'Hello,I am a global variable :)';
(function(){
var myGrades = [93,95,98,0,55,91];
var average = function() {
var total = myGrades.reduce(function(accumulator, item) {
return accumulator + item}, 0);
return 'Your average grade is ' + total / myGrades.length + '.';
}
var failing = function(){
var failingGrades = myGrades.filter(function(item) {
return item < 70;});
return 'You failed ' + failingGrades.length + ' times.';
}
console.log(failing());
console.log(global);
}())
// 'You failed 2 times.'
// 'Hello, I am a global variable :)'
記得匿名函數(shù)的括號是必須的,因為語句開始的關(guān)鍵詞function重視被認為是函數(shù)聲明,因此,包裹的圓括號創(chuàng)建了一個函數(shù)表達式.
例子2:全局導入
另一個比較流行的方法使用庫就像jq的全局導入,和我們剛剛看到的匿名閉包是一樣的,以參數(shù)的形式傳入全局:
(function(globalVariable){
var privateFunction = function(){
console.log('shhhh,this is private')
}
globalVariable.each = function(collection,iterator){
if(Array.isArray(collection)){
for(var i=0;i< collection.length;i++){
iterator(collection[i],i,collection);
}
}else{
for(var key in collection){
iterator(collection[key],key,collection);
}
}
};
globalVariable.filter = function(collection,test){
var filtered = [];
globalVariable.each(collection,function(item){
if(test(item)){
filter.push(item);
}
});
return filtered;
};
globalVariable.map = function(collection,iterator){
var mapped = [];
globalUtils.each(collection,function(value,key,collection){
mapped.push(iterator(value))
});
return mapped;
};
globalVariable.reduce = function(collection,iterator,accumulator){
var startingValueMissing = accumulator === undefined;
globalVariable.each(collection,function(item){
if(startingValueMissing){
accumulator = item;
startingValueMissing = false;
}else{
accumulator = iterator(accumulator,item);
}
});
return accumulator;
}
}(globalVariable))
在這個例子中,globalVariable是唯一一個全局變量,這種方法的好處在于可以先聲明全局變量,使他更容易被辨認.
例子3:對象接口
另外一種方法是創(chuàng)建一個模塊使用自備的對象接口,下面這樣子:
var myGradesCalculate = (function(){
var myGrades = [93, 95, 88, 0, 55, 91];
return {
average:function(){
var total = myGrades.reduce(function(acc,item){
return acc + item;
},0);
return 'Your average grade is'+total/myGrade.length+'.';
},
failing:function(){
var failingGrades = myGrades.filter(function(item){
return item<70;
});
return 'You"ve failed'+failingGrades.length + 'times.'
}
}
})();
myGradesCalculate.failing();
myGradesCalculate.average();
正如你所看到的,這種方法讓我們決定了什么變量/方法是我們想要保持private,什么方法使我們想要返回并暴露的.
例子4:顯示模塊模型
這個和以上的方法是非常的相似的,除了他確信讓所有的方法和變量保持私有直到explicitly exposed:
var myGradesCalculate = (function(){
var myGrades = [93, 95, 88, 0, 55, 91];
var average = function(){
var total = myGrades.reduce(function(acc,item){
return acc+item;
},0);
return 'Your average grade is '+total/myGrades.length+'.';
};
var failing = function(){
var failingGrades = myGrades.filter(function(item){
return item<70;
});
return 'you failed'+failingGrades.length+'times.';
};
return {
average:average,
failing:failing
}
})();
myGradesCalculate.failing();
myGradesCalculate.average();
看上去有很多東西需要掌握,但是這只是冰山一角.
CommonJS 和AMD
以上的方法都有一個共同點:一個全局變量包裹他的代碼在函數(shù)內(nèi),因此創(chuàng)建私人命名空間為他自己使用一個封閉的作用域.
然而每個方法都按自己的方式是生效,他們有自己的downsides.
首先,作為一個開發(fā)者,你需要直到你的文件載入的順序和依賴,例如:你正在使用backbone到你的項目,所以你在你的文件中包含了backbone的源碼.
但是,由于backbone對underscoreJS有很深的依賴,所以backbone的script的標簽不能放在underscore的script的標簽的前面.
作為一個開發(fā)者,管理依賴并把這些弄正確也是一個頭疼的問題.
另一個缺憾就是他們?nèi)匀粫鹈臻g的沖突,比如萬一你的兩個模塊有相同的名字怎么辦?或者說你有同一個模塊的兩個不同的版本,但是你兩個都需要?
所以,你會疑惑:我們可以設(shè)置一種方式去請求模塊的接口而不是直接通過全局作用域?
幸運的是,答案是yes,
有兩種比較流行的方式:commonJS和AMD
CommonJS
AMD