javascript 之模塊化篇

什麼是模塊化?

模塊化就是把系統分離成獨立功能的方法,這樣咱們須要什麼功能,就加載什麼功能。javascript

優勢:
可維護性:根據定義,每一個模塊都是獨立的,良好設計的模塊會盡可能與外部的代碼撇清關係,以便於獨立對其進行改進和維護。
可複用性:能夠重複利用,而不用常常複製本身以前寫過的代碼html

原始JS開發問題

一、污染全局變量
//a.js 文件:前端

var test1='aaaaaa';
//b.js 文件
var test1='bbbbbb';
 <script>
    console.log('test1='+test1);//bbbbbb;
 
</script>
console test1 輸出'bbbbbb';悲劇啊

二、命名衝突java

//a.js 文件:
function fun(){
    console.log('this is b');
}
 //b.js 文件
 
function fun(){
    console.log('this is b');
}
//main.js 文件
<script src="a.js"></script>
<script src="b.js"></script>
<script>
    fun();//this is b;
</script>
小張在a.js定義了fun(),小李在b.js又定義了fun(),a,b被小王引入到main.js,執行fun(),輸出this is b;

三、依賴關係
b.js依賴a.js,標籤的書寫順序必須是:node

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>

這樣在多人開發的時候很難協調啊,使人頭疼的問題。jquery

解決衝突的方式

一、使用java式的命名空間
二、變量前加「_」
三、對象寫法git

var module1={
    test1:'aaaaaa',
    fun:function(){
        console.log(this.test1);
    }
}
變量和函數封裝在對象裏面,使用時,調用對象的屬性便可:
module1.fun();//aaaaaa
可是這樣的寫法會暴露全部模塊成員,內部狀態能夠被外部改寫,
module1.test1='cccccc';

四、匿名閉包函數es6

var  module1=(function(){
    var test1='aaaaaa';
    var fun=function(){
        console.log('this is a');
    }
    return{
        fun:fun
    }
}());

匿名函數有本身的做用域,這樣外部代碼沒法讀取 module1 function 裏面的變量了,從而也不會修改變量或者是覆蓋同名變量了,可是仍是有缺陷的,module1這個的變量仍是暴露到全局了,而去隨着模塊的增多,全局變量會愈來愈多。
五、全局引入
像jquery庫使用的全局引入。和匿名閉包函數類似,只是傳入全局變量的方法不一樣
(function(window){github

var test1='aaaaaa';
window.testFun=function(){//經過給window添加屬性而暴漏到全局
    console.log(test1);
}

}(window));web

經過匿名函數包裝代碼,所依賴的外部變量傳給這個函數,在函數內部可使用這些依賴,而後在函數的最後把模塊自身暴漏給window。

3,4,5解決方法都是經過定一個全局變量來把全部的代碼包含在一個函數內,由此來建立私有的命名空間和閉包做用域。

本文着重介紹幾種廣受歡迎的解決方案:CommonJS,AMD,CMD,ES模塊化。

CommonJs

根據CommonJs規範,每一個文件就是一個模塊,有本身的做用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其餘文件不可見。

commonJS中模塊能夠加載屢次,可是隻會在第一次加載的時候運行一次,而後運行結構被緩存,再次加載就是讀取緩存的結果。

CommonJS規範加載模塊是同步的,也就是說,加載完成才能夠執行後面的操做,Node.js主要用於服務器編程,模塊通常都是存在本地硬盤中,加載比較快,因此Node.js採用CommonJS規範。

CommonJS規範分爲三部分:module(模塊標識),require(模塊引用), exports(模塊定義),
module變量在每一個模塊內部,就表明當前模塊;
exports屬性是對外的接口,用於導出當前模塊的方法或變量;
require()用來加載外部模塊,讀取並執行js文件,返回該模塊的exports對象;

一、commonJs模塊定義

module.exports定義模塊:

//math.js
let add=(x,y)=>{
    return x+y;
}
let sub=(x,y)=>{
    return x-y;
}

module.exports={
    add:add,
    sub:sub
};

exports 定義模塊:

let add=(x,y)=>{
    return x+y;
}
let sub=(x,y)=>{
    return x-y;
}
exports.add=add;
exports.sub=sub;

注意:不能夠直接對exports賦值,exports=add;

exports和module.exports有什麼區別呢?
在每一個模塊中Node都提供了一個Module 對象,表明當前模塊。

//console.log(Module);
Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/Users/zss/node-Demo/my-app/testNOde/b.js',
  loaded: false,
  children: [],
  paths: 
   [ '/Users/zss/node-Demo/my-app/testNOde/node_modules',
     '/Users/zss/node-Demo/my-app/node_modules',
     '/Users/zss/node-Demo/node_modules',
     '/Users/zss/node_modules',
     '/Users/node_modules',
     '/node_modules' 
     ] 
   }

module.exports屬性表示當前模塊對外輸出的接口,其餘文件加載該模塊,實際上就是讀取module.exports變量。
爲了方便,Node爲每一個模塊提供一個exports變量,指向module.exports。咱們把它們都打印出來看看究竟,

//test.js
console.log(module.exports);
console.log(exports);
console.log(module.exports===exports);

exports.test = ()=>{
    console.log('exports 1');
};
module.exports.test1 = ()=>{
    console.log('module.exports 1');
};
console.log(module.exports);
console.log(exports);

//輸出:
{}
{}
true
{ test: [Function], test1: [Function] }
{ test: [Function], test1: [Function] }

從上例能夠看出:
1.每一個模塊文件一建立,有個var exports = module.exports = {};使exports和module.exports都指向一個空對象。
**2.module是全局內置對象,exports是被var建立的局部對象,module.exports和exports所指向的內存地址相同
全部的exports收集到的屬性和方法,都賦值給了Module.exports,最終返回給模塊調用的是module.exports而不是exports。**

再舉個例子:

//test.js
exports.test = ()=>{
    console.log('exports 1');
};
module.exports={
    test:function(){
        console.log('module.exports 1');
    },
    testmodule:()=>{
        console.log('module.exports 2')
    }
}
console.log(module.exports);
console.log(exports);

 
 //輸出
{ test: [Function: test], testmodule: [Function: testmodule] }
{ test: [Function] }

//在index.js文件中調用test2.js
let a=require('./test2');
a.test();
a.testmodule();
//輸出:
module.exports 1
module.exports 2

全部的exports收集到的屬性和方法,都賦值給了Module.exports,當直接把函數和屬性傳給module.exports時,module.exports與exports不想等了,在調用時候,exports的屬性和方法會被忽略,因此最終返回給模塊調用的是module.exports而不是exports。

二、模塊分類

NodeJs的模塊分爲兩類:
一類是原生模塊,例如http,fs,path 等等。node在加載原生模塊的時候,不須要傳入路徑,NodeJs將原生模塊的代碼編譯到了二進制執行文件中,加載速度快。
一類是文件模塊,動態加載模塊,
可是NodeJs對原生模塊和文件模塊都進行了緩存,第二次require時,就是執行的內存中的文件。

三、commonJs模塊加載規則

index.js調用math模塊:

let math=require('./math');
let test=math.add(3,3);
console.log(test);

執行index.js 輸出:6;

當咱們執行node index.js的時候,第一語句就是「require('./math');」 加載 math文件。加載math文件這個動做是由原生模塊module的runMain()實現的。

有沒有注意到上面寫的是加載math文件,並無明確指出是js文件。
NodeJS加載文件模塊基本流程:
一、根據名稱按照‘.js’,‘.node‘,’.json‘的順訊依次查找,若是是.node或者.json的文件最好加上擴展名,加載速度快。
二、查找到math.js,讀取js內容,將使用function進行包裝,這樣能夠避免污染全局環境,該函數的參數包括require、module、exports等等參數,以mathi.js爲例:

(function(exports,require,module,__filename,__dirname){
        let add=(x,y)=>{
            return x+y;
        }
        let sub=(x,y)=>{
            return x-y;
        }

        module.exports={
            add:add,
            sub:sub
        };

 })

require 方法中的文件查找規則很複雜底,在網上copy了一個圖:

clipboard.png

更詳細的加載規則能夠參考:http://www.infoq.com/cn/artic...

四、commonJs模塊的加載機制:

//lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
//index.js
var mod=require('./lib');
consoe.log(mod.counter);
mod.incCounter();
consoe.log(mod.counter);

輸出:3
     3

commonJS中模塊加載之後,它的內部變化不會影響其內部變量,由於它們會被緩存,因此它輸出的是值的拷貝。

CommonJS規範比較適用服務器端,若是是瀏覽器就須要異步加載模塊了,因此就有了AMD,CMD解決方案。

AMD(requireJS)

 AMD是"Asynchronous Module Definition"的簡寫,也就是異步模塊定義。它採用異步方式加載模塊。經過define方法去定義模塊,require方法去加載模塊。

AMD模塊定義:

define(function(){
    let add=(x,y)=>{
        return x+y;
    }
    let sub=(x,y)=>{
        return x-y;
    }
    
    return {
        add:add,
        sub:sub
    };
});

若是這個模塊還須要依賴其餘模塊,那麼define函數的第一個參數,必須是一個數組,指明該模塊的依賴。

define([tools],function(){
    //…………………………
})

AMD模塊的加載:

require([module], callback);

第一個參數[module],是一個數組,裏面的成員就是要加載的模塊;第二個參數callback,則是加載成功以後的回調函數。例如加載math.js。

require([math],function(){
    //……………………
})

require()異步加載math,瀏覽器不會失去響應;它指定的回調函數,只有前面的模塊都加載成功後,纔會運行,解決了依賴性的問題。

CMD(SeaJS)

玉伯提出的CMD規範,並開發了前端模塊化開發框架SeaJS,不過在2015年後SeaJS中止了在github上維護,CMD與AMD用法很類似,可是我我的更喜歡使用SeaJS,雖然在2016年後也被我拋棄啦。

SeaJs使用:

// 全部模塊都經過 define 來定義
define(function(require, exports, module) {

   // 經過 require 引入依賴
   var $ = require('jquery');
   var Spinning = require('./spinning');

   // 經過 exports 對外提供接口
   exports.doSomething = ...

   // 或者經過 module.exports 提供整個接口
   module.exports = ...

});

有關於SeaJS與 RequireJS 的異同,能夠參考:
https://github.com/seajs/seaj...
https://www.douban.com/note/2...

ES6 模塊化

在es6 以前沒有模塊化的,爲了解決問題,提出了commonJS,AMD,CMD,如今ES6模塊化汲取了CommonJS 和 AMD 的優勢,簡潔的語法,異步加載
它徹底能夠成爲瀏覽器和服務器通用的模塊化解決方案。

ES6中模塊的定義

ES6 新增了兩個關鍵字 export 和 import,export 用於把 模塊裏的內容 暴露 出來, import 用於引入模塊提供的功能。

export命令輸出變量:

//lib.js
let bar=function(){
    console.log('this is bar funciton');
};

let foo=function(){
    console.log('this is foo function');
};

export {bar,foo}

上面的代碼還有另外一種寫法:

export let bar=function(){
    console.log('this is bar funciton');
};

export let foo=function(){
    console.log('this is foo function');
};

export 不止能夠導出函數,還能夠導出對象,類,字符串等等

const test='aaa';
const obj={
    str:'hello!'
}
export {test,obj};

注:使用export在尾部輸出變量時,必定要加大括號,

ES6中模塊的加載

import 加載模塊:

//加載 lib.js文件
 import {bar,foo,test,obj} from './lib'
 
 foo();//this is foo function

注:import 命令具備提高效果,會提高到整個模塊的頭部,首先執行

上面的是逐一指定要加載的方法,咱們還可使用 * 能夠總體加載模塊:

import * as lib from './lib'
lib.foo();

上面的加載模塊的方式須要知道變量名和函數名,不然是沒法加載的,咱們可使用export default 命令,爲模塊指定默認輸出。

//lib.js
let foo=function(){
    console.log('this is foo');
} 
export default foo;

其餘文件加載時,能夠爲該匿名函數指定任意名字。

import  lib from 'lib';

注:export default 命令適用於指定默認模塊的輸出,一個模塊只能有一個默認輸出,因此export default 只能使用一次。

ES6 模塊運行機制

ES6模塊是動態引用,若是使用import從一個模塊加載變量(即import foo from 'foo'),變量不會被緩存,而是成爲一個指向被加載模塊的引用。等腳本執行時,根據只讀引用,到被加載的那個模塊中去取值。
舉一個NodeJS模塊化的例子:

//lib.js
export let counter = 3;
exoprt function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
//index.js
import {counter,incCounter} from './lib';
consoe.log(mod.counter);
mod.incCounter();
consoe.log(mod.counter);

輸出:3
     4
調用 incCounter()方法後,lib 模塊裏的counter變量值改變了。

參考:
http://www.cnblogs.com/TomXu/...
http://blog.csdn.net/tyro_jav...
http://javascript.ruanyifeng....
http://www.ruanyifeng.com/blo...
https://zhuanlan.zhihu.com/p/...
https://segmentfault.com/a/11...
http://web.jobbole.com/83761/
http://es6.ruanyifeng.com/#do...
http://www.cnblogs.com/lishux...

相關文章
相關標籤/搜索