如果你對模塊化已經瞭解,可以直接從第三點開始閱讀。
一、模塊化的概念:
在進行項目分析或者編碼時,先把項目進行拆分,拆分成很多的類,對象,很多的函數等等。能夠提高代碼的複用性。這些被拆分出來的類型,對象,函數就是模塊。就像一輛汽車是由很多的零部件組成,每個零部件就是一個小模塊,而由很多零件組成的發動機之於一輛汽車可以認爲是一個大模塊;或者說,一臺計算機,由主板,cpu,內存,硬盤,顯示器,鍵盤等大模塊組成。而一個內存或者cpu又分別由很多的小模塊組成。以模塊爲單位管理代碼,會更加獨立,調試方便,維護也很方便。
二、模塊化經歷的階段:
1. 函數封裝
這種做法的缺點很明顯:污染了全局變量,無法保證不與其他模塊發生變量名衝突,而且模塊成員之間沒什麼關係。不同js文件裏有相同的全局變量,如果被引入到同一個html文件中,全局變量就會互相影響。
2. 對象
這樣避免了變量污染,只要保證模塊名唯一即可,同時同一模塊內的成員也有了關係,看似不錯的解決方案,但是也有缺陷,外部(對象之外)可以隨意修改內部成員(屬性)
對象的屬性就是對象中每個方法的全局變量。
var p = {
id:"007",
name :"芙蓉",
age:25,
eat:function(str){
alert(this.name+"在喫"+str);
},
work:function(str){
alert(this.name+“在幹"+str);
}
}
//存在問題:
//如:對於p對象的年齡,有效取值應該是在0-150之間的整數。
// 而以下代碼的執行,
p.age = 151;
都會使得項目內部出現了不合法的數據。這是程序的健壯性不好。
所以,對於對象的成員變量(屬性),應該不能被外部訪問纔對。
3. 立即執行函數
可以通過立即執行函數,來達到隱藏細節的目的
var p = (function (){
var name="芙蓉";
var age=25;
function eat(str){
alert(this.name+"在喫"+str);
}
function setAge(age){
if(age<0 || age>150){
alert("親,年齡超出有效值(0-150)的範圍");
return;
}
age = age;
}
function getAge(){
return age;
}
return {
eat:eat,
setAge:setAge,
getAge:getAge
}
})();
var p = (function (){
var name="芙蓉";
var age=25;
function eat(str){
alert(this.name+"在喫"+str);
}
function setAge(age){
if(age<0 || age>150){
alert("親,年齡超出有效值(0-150)的範圍");
return;
}
age = age;
}
function getAge(){
return age;
}
return {
eat:eat,
setAge:setAge,
getAge:getAge
}
})();
4. 模塊化的解決方案。
- 以上模塊化存在的問題:
不管是以上哪種方式(函數,對象,立即執行函數),都存在同樣的問題:
1)、html不但要引入自己需要的js文件,還需要引入js文件需要的js文件。如:a.html需要使用 b.js的代碼,而由於b.js中使用了c.js裏的代碼。所以,在a.html中必須引入b.js和c.js。這是JavaScript語言先天性的缺陷----js文件沒法引入js文件。其它編程語言(java,c#,c/c++)就不存在這個問題。
2)、引入js文件的順序問題以及異步加載問題。
- 爲此出現了一些解決方案:
1)、前端模塊化
在前端裏出現了第三方的解決方案 AMD和CMD
2)、後端模塊化
在後端裏(nodeJS)出現了commonJS規範。
三、模塊化的概念
1、導出:導出就是對外開放的,可以導出變量,常量,函數,對象等等。使用export關鍵字。放在export關鍵字後面的(即對外導出的)變量,常量,函數和對象,在其它js文件中可以使用,否則,其它js文件中是不能使用的。即只能內部使用的。
模塊化的這種思路,可以隱藏細節,開放接口
2、導入:導入就是把其它js文件引入到當前js文件裏。
四、前端模塊化:AMD和CMD
1、ES6之前,原生不支持模塊化,所以出現了第三方的解決方案,分別是AMD和CMD
AMD: Asynchronous Module Definition,中文名是異步模塊定義的意思。它是一個在瀏覽器端模塊化開發的規範,由於不是JavaScript原生支持,使用AMD規範進行頁面開發需要用到對應的庫函數(RequireJS庫),實際上AMD 是 RequireJS 在推廣過程中對模塊定義的規範化的產出
CMD: ”Common Module Definition”,稱爲 通用模塊加載規範 。一般也是用在瀏覽器端。瀏覽器端異步加載庫 Sea.js 實現的就是CMD規範。
2、AMD之requireJS
1)、引入第三方庫requireJS
<script src="js/require.js" data-main="js/main.js"></script>
2)、html文件中引入主模塊
主模塊就是html文件所(直接)依賴的模塊(文件),使用script標籤的data-main屬性。假如html需要依賴的文件是main.js , 則代碼如下:
<script src="js/require.js" data-main="js/main.js"></script>
如果main.js需要引入其它js文件,那怎麼辦?我們就需要知道如何定義模塊,如何引入模塊。
即html文件的最終代碼是:<
!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input id="btn01" type="button" value="測試" />
</body>
</html>
<script src="js/require.js" data-main="js/main.js"></script>
3)、主模塊引入其它模塊(就是導入)main.js代碼
假如,main.js引入的是person.js。那麼 main.js 裏的寫法如下:
//require :引入其它模塊
//require函數的第一個參數:是引入的模塊名,是數組
//requrie函數的第二個參數,是回調函數,回調函數的參數,
//對應到模塊裏的return。 即 p 對應着js/person.js裏的return。
require(["js/person.js"],function(p){
document.getElementById("btn01").onclick = function(){
p.eat("方便麪");
p.work();
}
});
4)、定義模塊(person.js)
導出用return,引入別的模塊,在define的第一個參數寫就行。
假如,main.js引入的是person.js。則person.js裏的代碼寫法如下:
// 這表示當前模塊不依賴其它模塊
define([],function(){
var name ="張三瘋";
var age = 12;
return {
eat:function(str){
alert(name+"在喫"+str);
},
work:function(){
alert(name+"在努力地工作……");
}
}
});
5)、如果person.js模塊還需要其它模塊(如:dog.js)的話,怎麼辦?
5.1)、修改一下,person.js的代碼。
//define :定義模塊
//define 函數的第一個參數:是引入(依賴)的模塊名,是數組
//define 函數的第二個參數,是回調函數,回調函數的參數,
// 對應到模塊裏的return。 即 d 對應着js/dog.js裏的return。
define(["js/dog.js"],function(d){
var name ="張三瘋";
var age = 12;
return {
eat:function(str){
alert(name+"在喫"+str);
},
work:function(){
alert(name+"在努力地工作……");
},
lookDoor:function(){
d.lookDoor();
}
}
});
5.2)、增加一個dog.js的代碼。
//這表示當前模塊不依賴其它模塊
define(function(){
var name ="大黃";
return {
lookDoor:function(){
alert(name+"在看門");
}
}
});
3、CMD之seaJS
1)、引入seaJS(在html裏):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input id="btn01" type="button" value="測試" />
</body>
</html>
<script src="js/sea.js"></script>
<script src="js/main_sea.js"></script>
2)、主模塊的引入:
<script src="js/main_sea.js"></script>
3)、主模塊引入其它模塊(main_sea.js代碼),
假如main_sea.js引入person_sea.js
//在主模塊裏,用seajs引入其它模塊,用seajs.use函數。
seajs.use(["person_sea"],function(p){
document.getElementById("btn01").onclick = function(){
p.eat("方便麪");
p.work();
p.lookDoor();
}
});
4)、模塊的定義(person_sea.js):
//seajs中定義模塊,用define函數。
//引入其它模塊:不在第一個參數,使用回調函數裏的requrie;
//導出本模塊的屬性或者方法:使用回調函數裏的exports
define(function(require,exports) {
var name ="張三瘋";
var age = 12;
//對外導出(開發)eat函數
exports.eat = function(str){
alert(name+"在喫"+str);
};
//對外導出(開發)work函數
exports.work = function(){
alert(name+"在努力地工作……");
};
//此時需要dog模塊,所以,用require引入。
let d = require("dog_sea");
exports.lookDoor=function(){
d.lookDoor();
}
});
dog_sea.js的代碼。
define(function(require,exports) {
var name ="大黃";
exports.lookDoor=function(){
alert(name+"在看門");
}
});
4、總結一下,使用模塊化的步驟:
1)、定義模塊
1.1)、引入別的模塊
1.2)、對外導出本模塊的哪些屬性和方法
2)、主模塊引入其它模塊
3)、html引入主模塊
5、對比AMD和CMD的區別:
5.1)、定義模塊
都使用的都是define函數
5.1.1)、引入別的模塊
AMD:
在define函數的第一個參數
define([依賴的模塊列表],function(引入模塊的變量名列表){});
CMD:
在define函數的第二個參數(回調函數)的require參數。
define(function(require,exports){
let d = require("dog");//引入dog模塊
});
5.1.2)、對外導出本模塊的哪些屬性和方法
AMD:
用return
CMD:
在define函數的第二個參數(回調函數)的exports參數。
5.2)、主模塊引入其它模塊
AMD:
require函數
require([引入的其它模塊列表],function(其它模塊對應的變量名列表){
});
CMD:
seajs.use([引入的其它模塊列表],function(其它模塊對應的變量名列表){
});
5.3)、html引入主模塊(假如主模塊是main.js)
AMD:
1)、script標籤的 data-main
<script src="js/require.js" data-main="js/main.js"></script>
2)、也可以使用script標籤引入主模塊。
即:
<script src="js/require.js" ></script>
<script src="js/main.js" ></script>
CMD:
script標籤引入主模塊
<script src="js/sea.js" ></script>
<script src="js/main.js" ></script>
5.4)、模塊路徑問題:
AMD:
基準路徑是html的文件夾
define(["js/dog.js"],function(d){})
CMD:
就是js文件的文件夾,
let d = require("dog_sea");
5.5)、依賴方面:
5.5.1). 對於依賴的模塊,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible(儘可能的懶惰)
5.5.2). CMD 推崇依賴就近,AMD 推崇依賴前置。
5.5.3)如下代碼示例:
a模塊依賴b模塊和c模塊的代碼格式
- 、AMD
a.js的代碼:
define(['./b', './c'], function(b, c) { // 依賴必須一開始就寫好
b.doSomething()
c.doSomething()
})
- 、CMD的代碼:
a.js
define(function(require,exports,module){
var b = require(“b");
………………
var c = require(“c");
………………
});
四、後端模塊化:commonJS
commonJS是後端模塊化的解決方案,在nodeJS中有使用(nodeJS是js做後端的技術,相當於php,jsp,aspx)。
咱們曾經寫的webpack就是用的commonJS規範。
1、導出:
module.expors={
Name:‘張三瘋’,
Eat:function(){
}
}
2、導入
Require();
五、ES6的模塊化:
1、原生支持模塊化了
ES6中新增的模塊化,即從ES6開始,原生js支持模塊化了,不需要第三方的模塊化庫,現在很多瀏覽器也支持ES6了。
2、模塊化的兩個概念
1)、導出(export關鍵字):導出就是對外開放的,可以導出變量,常量,函數,對象等等。使用export關鍵字。放在export關鍵字後面的(即對外導出的)變量,常量,函數和對象,在其它js文件中可以使用,否則,其它js文件中是不能使用的。即只能內部使用的。
在用export導出時,可以導出多個
如:person.js文件(模塊)裏,如下:
//導出字符串
export var str = "hello";
//導出函數
export var fun = function(){
alert("我是函數");
}
//導出對象
export const p = {
"id":"007",
"name":"張三瘋",
"eat":function(){
alert("喫");
ff();
}
}
//此函數沒有使用export關鍵字導出,所以,只能在當前js文件內部使用
function ff(){
alert("我只能在當前js被使用");
}
2)、導入(import):導入就是把其它js文件引入到當前js文件裏。使用關鍵字import。
在使用import導入(export導出的)時,要使用花括號,
如:import {str,fun,p} from './person.js';
在index.js文件中,引入模塊person.js;
//導入時,需要使用{},這是解構賦值。
import {str,fun,p} from './person.js';
window.onload = function(){
document.getElementById("btn01").onclick = function(){
console.log(str);
fun();
console.log(p.id);
p.eat();
}
}
3)、在html文件中引入index.js(注意: type="module")
注意:js中使用了模塊化的關鍵字import,在引入時,script標籤的type屬性的值必須寫成module。即:<script type="module" src="js/index.js"></script>
<body>
<input id="btn01" type="button" value="測試" />
</body>
<script type="module" src="js/index.js"></script>
注意:測試以上代碼時,google瀏覽器要求放在服務器上進行 ,否則,就會有跨域問題。
4)、export default和export 有什麼區別:
- 、export與export default均可用於導出常量、函數、文件、模塊等
- 、在一個文件或模塊中,export可以有多個,export default僅有一個,而且export default在導出是不需要變量名,相當於匿名的。
- 、通過export方式導出,在導入時要加{ },export default則不需要。
代碼示例:
模塊定義:dog.js
export default {
"name":"大黃",
"eat":function(){
alert("喫");
}
}
導入模塊:
import d from './dog.js';
window.onload = function(){
document.getElementById("btn01").onclick = function(){
console.log(d);
d.eat();
}
}